diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..2263a3c6 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +charset = utf-8 +indent_size = 4 +indent_style = space +max_line_length = 150 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{kt,kts}] +ij_kotlin_allow_trailing_comma = true +ij_kotlin_allow_trailing_comma_on_call_site = true +ktlint_function_naming_ignore_when_annotated_with = Composable +ktlint_standard_filename = disabled diff --git a/.github/workflows/android-cd.yml b/.github/workflows/android-cd.yml new file mode 100644 index 00000000..8b55492a --- /dev/null +++ b/.github/workflows/android-cd.yml @@ -0,0 +1,105 @@ +name: Android CD + +on: + push: + branches: + - main + +jobs: + cd-build: + + runs-on: ubuntu-latest + + permissions: + contents: write + + outputs: + version_tag: ${{ steps.extract_version_name.outputs.version }} + release_notes: ${{ steps.release_note_content.outputs.notes }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + cache: gradle + + - name: Setup Android SDK + uses: android-actions/setup-android@v2 + + - name: Generate local.properties + run: echo '${{ secrets.LOCAL_PROPERTIES }}' | base64 -d > ./local.properties + + - name: Generate keystore.properties + run: echo '${{ secrets.KEYSTORE_PROPERTIES }}' | base64 -d > ./keystore.properties + + - name: Generate metasearch.jks + run: echo '${{ secrets.JAVA_KEY_STORE }}' | base64 -d > ./metasearch.jks + + - name: Extract Version Name from libs.versions.toml + id: extract_version_name + run: | + set -euo pipefail + VERSION=$(grep "versionName" gradle/libs.versions.toml | sed -E 's/.*versionName\s*=\s*"([^"]+)".*/\1/') + if [[ -z "$VERSION" ]]; then + echo "Error: Not Found versionName" >&2 + exit 1 + fi + echo "version=v${VERSION}" >> "$GITHUB_OUTPUT" + echo "Version extracted from toml: v${VERSION}" + + - name: Generate Simple Release Note + id: release_note_content + run: | + set -euo pipefail + RELEASE_COMMIT_SHA=$(git log -n 1 --grep="^release:" --pretty=format:%H) + + if [[ -z "$RELEASE_COMMIT_SHA" ]]; then + echo "Error: 'release:' prefix commit not found." >&2 + exit 1 + else + NOTES=$(git log -1 --format=%b $RELEASE_COMMIT_SHA | sed -e '/^\s*$/d' -e 's/^[[:space:]]*//') + echo "Release commit SHA: $RELEASE_COMMIT_SHA" + fi + + echo "notes<> $GITHUB_OUTPUT + echo -e "$NOTES" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Build Release AAB + run: ./gradlew :app:bundleRelease + + - name: Upload AAB + uses: actions/upload-artifact@v4 + with: + name: app-release.aab + path: app/build/outputs/bundle/release/app-release.aab + if-no-files-found: error + + deploy: + needs: cd-build + runs-on: ubuntu-latest + steps: + - name: Download AAB + uses: actions/download-artifact@v4 + with: + name: app-release.aab + path: app/build/outputs/bundle/release/ + + - name: Create Github Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ needs.cd-build.outputs.version_tag }} + release_name: ${{ needs.cd-build.outputs.version_tag }} + body: ${{ needs.cd-build.outputs.release_notes }} + files: app/build/outputs/bundle/release/app-release.aab + generate_release_notes: true diff --git a/.github/workflows/android-ci.yml b/.github/workflows/android-ci.yml new file mode 100644 index 00000000..a2e685ef --- /dev/null +++ b/.github/workflows/android-ci.yml @@ -0,0 +1,151 @@ +name: Android CI + +on: + pull_request: + +permissions: + contents: read + pull-requests: write + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + ci-build: + + runs-on: ubuntu-latest + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Setup JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + cache: gradle + + - name: Setup Android SDK + uses: android-actions/setup-android@v2 + + - name: Cache Gradle Wrapper + uses: actions/cache@v4 + with: + path: ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle-wrapper- + + - name: Cache Build Cache + uses: actions/cache@v4 + with: + path: ~/.gradle/caches/build-cache-1 + key: ${{ runner.os }}-build-cache-${{ hashFiles('**/build.gradle*', '**/gradle-wrapper.properties') }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-build-cache-${{ hashFiles('**/build.gradle*', '**/gradle-wrapper.properties') }} + ${{ runner.os }}-build-cache- + + - name: Generate local.properties + run: echo '${{ secrets.LOCAL_PROPERTIES }}' | base64 -d > ./local.properties + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Code Style Check + id: ktlint + run: | + start=$(date +%s) + ./gradlew ktlintCheck detekt --parallel + end=$(date +%s) + echo "time=$((end-start))" >> $GITHUB_OUTPUT + + - name: Unit Test + id: test + run: | + start=$(date +%s) + ./gradlew testDebugUnitTest --parallel + end=$(date +%s) + echo "time=$((end-start))" >> $GITHUB_OUTPUT + + - name: Debug Build with Gradle + id: assemble + run: | + start=$(date +%s) + ./gradlew buildDebug --stacktrace --build-cache --parallel + end=$(date +%s) + echo "time=$((end-start))" >> $GITHUB_OUTPUT + + - name: Create CI summary + if: always() + uses: actions/github-script@v7 + with: + script: | + const results = { + test: { + status: '${{ steps.test.outcome }}', + name: 'Unit Test', + time: '${{ steps.test.outputs.time }}' + }, + assemble: { + status: '${{ steps.assemble.outcome }}', + name: 'Debug Build', + time: '${{ steps.assemble.outputs.time }}' + }, + ktlint: { + status: '${{ steps.ktlint.outcome }}', + name: 'Code Style Check', + time: '${{ steps.ktlint.outputs.time }}' + } + }; + + const emoji = { + success: '✅', + failure: '❌', + cancelled: '⚠️', + skipped: '⏭️' + }; + + const runUrl = `https://github.com/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`; + + function formatTime(seconds) { + seconds = parseInt(seconds || 0, 10); + if (seconds >= 60) { + const min = Math.floor(seconds / 60); + const sec = seconds % 60; + return `${min}m ${sec}s`; + } + return `${seconds}s`; + } + + const totalTime = Object.values(results) + .reduce((acc, step) => acc + parseInt(step.time || 0), 0); + + let body = `## 🤖 Android CI Summary\n\n**Step Results:**\n`; + for (const step of Object.values(results)) { + const statusEmoji = emoji[step.status] || step.status; + const statusText = step.status === 'success' ? 'Success' : (step.status === 'failure' ? 'Failure' : 'Skipped'); + body += `- **${step.name}**: ${statusEmoji} ${statusText} (${formatTime(step.time)})\n`; + } + + body += `\n**Total Time:** **${formatTime(totalTime)}**\n`; + + const failedSteps = Object.values(results) + .filter(step => step.status !== 'success') + .map(step => step.name); + + if (failedSteps.length > 0) { + body += `\n⚠️ **Warning:** The following steps failed: **${failedSteps.join(', ')}**\n`; + body += `See the [Actions Log](${runUrl}) for details.\n`; + } else { + body += `\n🎉 All steps completed successfully!`; + } + + github.rest.pulls.createReview({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.issue.number, + event: 'COMMENT', + body + }); diff --git a/.gitignore b/.gitignore index aa724b77..bf38cff5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ *.iml .gradle -/local.properties +/.idea /.idea/caches /.idea/libraries /.idea/modules.xml @@ -12,4 +12,13 @@ /captures .externalNativeBuild .cxx + local.properties +keystore.properties +metasearch.jks + +**/*.pro + +/*/build +/*/*/build +/*/*/*/build diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index 17743842..00000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -MetaSearch \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index b589d56e..00000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml deleted file mode 100644 index 0c0c3383..00000000 --- a/.idea/deploymentTargetDropDown.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml deleted file mode 100644 index 0897082f..00000000 --- a/.idea/gradle.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/migrations.xml b/.idea/migrations.xml deleted file mode 100644 index f8051a6f..00000000 --- a/.idea/migrations.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 8978d23d..00000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1ddf..00000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/README.md b/README.md index bacf9f31..fc8fbe65 100644 --- a/README.md +++ b/README.md @@ -1 +1,108 @@ # Visualize Me By Photo + + + +## Demo + +### 일상 언어로 사진 검색 + + +### 등록한 인물 이름으로 사진 검색 + + +## Features +- Image Analysis +- Focusing Search +- Natural Language Search +- Face Analytics + +### Intelligent Search + +|자연어 검색|포커싱 검색| +|:---:|:---:| +| | | +|자연어를 통한 복잡한 조건으로 정확한 검색|간단한 드래그 동작으로 쉽고 빠른 검색| + +### Person In My Gallery + +|인물 정보 통합 및 관리|개인화| +|:---:|:---:| +| | | +|분석된 인물 통합 및 프로필 사진 변경|선호하는 인물로 등록하고 홈 화면에서 빠르게 접근| + +## Local Settings +- local.properties +```.properties +OPENAI_API_KEY= +DEBUG_WEB_SERVER_URL= +RELEASE_WEB_SERVER_URL= +DEBUG_AI_SERVER_URL= +RELEASE_AI_SERVER_URL= +``` + +## Tech Stack + +### Jetpack Libraries +- DataStore +- Room +- Splash +- WorkManager +- Paging3 + +### UI +- Jetpack Compose (Declarative UI framework) + +### Permissions +- Accompanist Permissions + +### DI +- Dagger/Hilt + +### Network And Image Loading +- Retrofit +- OkHttp3 +- Coil 3 + +### Code Quality +- Ktlint +- DeteKt + +### Architecture +- MVI (Model-View-Intent) with [Slack Circuit](https://github.com/slackhq/circuit) +- Module Strategy: Feature-based Multi-Module + +### Project Dependency Graph +project-dependency-graph + +## Project Structure +```text +. +├── app # 앱 실행 진입점 (Hilt Setup) +├── build-logic # Convention Plugins (Gradle 공통 설정 관리) +├── core # 공통 기능 모듈 (Shared Modules) +│ ├── common # 유틸리티, 상수, 공통 코드 +│ ├── data +│ │ ├── api +│ │ └── impl +│ ├── datastore # Preference DataStore 관리 +│ │ ├── api +│ │ └── impl +│ ├── designsystem # 공통 컴포넌트 및 테마 +│ ├── model # 도메인 모델 +│ ├── network # Retrofit 설정 및 네트워크 서비스 +│ ├── notification # 알림(Notification) 생성 및 관리 +│ ├── room # Room 로컬 데이터베이스 설정 +│ │ ├── api # DAO 인터페이스 +│ │ └── impl # Database 생성 및 Migration 로직 +│ └── ui +├── feature # 화면 단위 기능 모듈 (Feature Modules) +│ ├── detail +│ ├── graph # 갤러리 전체 데이터 시각화 화면 +│ ├── home # 홈 화면 (이미지 분석 워커 포함) +│ ├── main +│ ├── person # 인물 사진 모아보기 및 관리 +│ ├── screens # 메인 네비게이션 및 스크린 정의 +│ ├── search # AI 기반 자연어 및 포커싱 검색 화면 +│ └── splash +└── gradle # Version Catalog (libs.versions.toml) +``` diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 13fb7534..73f59c9c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,87 +1,45 @@ plugins { - id("com.android.application") + alias(libs.plugins.metasearch.android.application) + alias(libs.plugins.metasearch.android.application.compose) + alias(libs.plugins.metasearch.android.hilt) + // -------------리팩토링 중 id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin") } android { - namespace = "com.example.metasearch" - compileSdk = 34 - - defaultConfig { - applicationId = "com.example.metasearch" -// minSdk = 33 - minSdk = 30 - targetSdk = 34 - versionCode = 1 - versionName = "1.0" - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - } + namespace = "com.metasearch.android" +} - buildTypes { - release { - isMinifyEnabled = false - proguardFiles( - getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro" - ) - } - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } - buildFeatures { - viewBinding = true - dataBinding = true - buildConfig = true - } +ksp { + arg("circuit.codegen.mode", "hilt") } dependencies { - - // Retrofit dependency - implementation("com.squareup.retrofit2:retrofit:2.9.0") - // Gson dependency - implementation("com.squareup.retrofit2:converter-gson:2.9.0") - // OkHttp dependency - implementation("com.squareup.okhttp3:okhttp:4.9.2") - implementation("com.squareup.okhttp3:logging-interceptor:4.9.0") - - implementation("androidx.work:work-runtime:2.9.0") - - //neo4j dependency - implementation("org.neo4j.driver:neo4j-java-driver:4.4.0") - //fragment dependency - implementation("androidx.fragment:fragment-ktx:1.6.2") - //CircleImageView dependency - implementation("de.hdodenhof:circleimageview:3.1.0") - //PhotoView dependency - implementation("com.github.chrisbanes:PhotoView:2.3.0") - //SplashScreen dependency - implementation("androidx.core:core-splashscreen:1.0.1") - //SpinKit dependency - implementation("com.github.ybq:Android-SpinKit:1.4.0") - // Glide dependency - implementation("com.github.bumptech.glide:glide:4.16.0") - // CardView dependency - implementation("androidx.cardview:cardview:1.0.0") - // StyleableToast dependency - implementation("io.github.muddz:styleabletoast:2.4.0") - // ColorPickerDialog dependency - implementation("me.jfenn.ColorPickerDialog:base:0.2.2") - - implementation("androidx.appcompat:appcompat:1.6.1") - implementation("com.google.android.material:material:1.11.0") - implementation("androidx.constraintlayout:constraintlayout:2.1.4") - implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0") - implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0") - implementation("androidx.navigation:navigation-fragment:2.7.7") - implementation("androidx.navigation:navigation-ui:2.7.7") - implementation("androidx.cardview:cardview:1.0.0") - testImplementation("junit:junit:4.13.2") - androidTestImplementation("androidx.test.ext:junit:1.1.5") - androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") - implementation("androidx.work:work-runtime:2.9.0") - -} \ No newline at end of file + implementation(projects.core.common) + implementation(projects.core.data.api) + implementation(projects.core.data.impl) + implementation(projects.core.datastore.api) + implementation(projects.core.datastore.impl) + implementation(projects.core.room.api) + implementation(projects.core.room.impl) + implementation(projects.core.model) + implementation(projects.core.network) + implementation(projects.core.notification) + implementation(projects.core.ui) + implementation(projects.core.designsystem) + + implementation(projects.feature.screens) + implementation(projects.feature.splash) + implementation(projects.feature.home) + implementation(projects.feature.detail) + implementation(projects.feature.search) + implementation(projects.feature.main) + implementation(projects.feature.person) + implementation(projects.feature.graph) + implementation(projects.feature.webview) + + implementation(libs.bundles.circuit) + + api(libs.circuit.codegen.annotation) + ksp(libs.circuit.codegen.ksp) +} diff --git a/app/src/androidTest/java/com/example/metasearch/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/example/metasearch/ExampleInstrumentedTest.java deleted file mode 100644 index 36ffd3f2..00000000 --- a/app/src/androidTest/java/com/example/metasearch/ExampleInstrumentedTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.example.metasearch; - -import android.content.Context; - -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.junit.Assert.*; - -/** - * Instrumented test, which will execute on an Android device. - * - * @see Testing documentation - */ -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - @Test - public void useAppContext() { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - assertEquals("com.example.metasearch", appContext.getPackageName()); - } -} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 84037d92..4a095984 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -15,6 +15,7 @@ - - - - - - - - - - - + - \ No newline at end of file + + diff --git a/app/src/main/java/com/example/metasearch/data/dao/AnalyzedImageListDatabaseHelper.java b/app/src/main/java/com/example/metasearch/data/dao/AnalyzedImageListDatabaseHelper.java deleted file mode 100644 index 12641196..00000000 --- a/app/src/main/java/com/example/metasearch/data/dao/AnalyzedImageListDatabaseHelper.java +++ /dev/null @@ -1,112 +0,0 @@ -package com.example.metasearch.data.dao; - -import static androidx.fragment.app.FragmentManager.TAG; - -import android.annotation.SuppressLint; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.database.SQLException; -import android.util.Log; - -import java.util.ArrayList; - -public class AnalyzedImageListDatabaseHelper extends SQLiteOpenHelper { - private static final String DATABASE_NAME = "image_analyzer.db"; - private static final int DATABASE_VERSION = 1; - private static final String TABLE_NAME = "analyzed_images"; - private static final String COLUMN_ID = "id"; - private static final String COLUMN_IMAGE_PATH = "image_path"; - - // 싱글톤 인스턴스 - private static AnalyzedImageListDatabaseHelper instance; - - // 싱글톤 인스턴스 생성 메소드 - public static synchronized AnalyzedImageListDatabaseHelper getInstance(Context context) { - if (instance == null) { - instance = new AnalyzedImageListDatabaseHelper(context.getApplicationContext()); - } - return instance; - } - private AnalyzedImageListDatabaseHelper(Context context) { - super(context, DATABASE_NAME, null, DATABASE_VERSION); - } - -// public static AnalyzedImageListDatabaseHelper getInstance(Context context) { -// if (instance == null) { -// instance = new AnalyzedImageListDatabaseHelper(context.getApplicationContext()); -// } -// return instance; -// } - - // 생성자를 private으로 설정하여 외부에서 인스턴스화 방지 - - - @Override - public void onCreate(SQLiteDatabase db) { - String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + "(" - + COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," - + COLUMN_IMAGE_PATH + " TEXT UNIQUE NOT NULL)"; - db.execSQL(CREATE_TABLE); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME); - onCreate(db); - } - - @SuppressLint("RestrictedApi") - public void addImagePath(String imagePath) { - SQLiteDatabase db = this.getWritableDatabase(); - ContentValues values = new ContentValues(); - values.put(COLUMN_IMAGE_PATH, imagePath); - - try { - long result = db.insertOrThrow(TABLE_NAME, null, values); - if (result == -1) { - Log.e(TAG, "Failed to insert image path: " + imagePath); - } else { - Log.d(TAG, "Image path inserted successfully: " + imagePath); - } - } catch (SQLException e) { - Log.e(TAG, "Error adding image path: " + imagePath, e); - } finally { - db.close(); - } - } - - - public void removeImagePath(String imagePath) { - SQLiteDatabase db = this.getWritableDatabase(); - try { - db.delete(TABLE_NAME, COLUMN_IMAGE_PATH + " = ?", new String[]{imagePath}); - } catch (SQLException e) { - e.printStackTrace(); - } finally { - db.close(); - } - } - public ArrayList loadAnalyzedImages() { - ArrayList analyzed_image_list = new ArrayList<>(); - SQLiteDatabase db = this.getWritableDatabase(); - Cursor cursor = null; - try { - cursor = db.query(TABLE_NAME, new String[]{COLUMN_IMAGE_PATH}, null, null, null, null, null); - if (cursor == null) { - return new ArrayList<>(); // 또는 적절한 예외 처리 - } - analyzed_image_list = new ArrayList<>(); - while (cursor.moveToNext()) { - analyzed_image_list.add(cursor.getString(0)); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - return analyzed_image_list; - } -} diff --git a/app/src/main/java/com/example/metasearch/data/dao/DatabaseHelper.java b/app/src/main/java/com/example/metasearch/data/dao/DatabaseHelper.java deleted file mode 100644 index 69b3bc9d..00000000 --- a/app/src/main/java/com/example/metasearch/data/dao/DatabaseHelper.java +++ /dev/null @@ -1,427 +0,0 @@ -package com.example.metasearch.data.dao; - -import static androidx.constraintlayout.helper.widget.MotionEffect.TAG; - -import android.annotation.SuppressLint; -import android.content.ContentValues; -import android.content.Context; -import android.content.res.Resources; -import android.database.Cursor; -import android.database.SQLException; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.database.sqlite.SQLiteStatement; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.os.ParcelFileDescriptor; -import android.provider.CallLog; -import android.util.Log; - -import com.example.metasearch.data.model.Person; - -import java.io.ByteArrayOutputStream; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -public class DatabaseHelper extends SQLiteOpenHelper { - private final Context context; - static final String TABLE_NAME = "Faces"; - private static final int DATABASE_VERSION = 4; // 데이터베이스 버전 번호 증가 - private static final String COLUMN_IMAGE = "IMAGE"; // 이미지 컬럼 - private static final String COLUMN_INPUTNAME = "INPUTNAME"; // 인물 이름 컬럼 - private static final String COLUMN_NAME = "NAME"; // 이미지 이름 컬럼 - private static final String COLUMN_PHONENUMBER = "PHONENUMBER"; // 전화번호 컬럼 - private static DatabaseHelper instance; - - // DatabaseHelper 싱글톤 생성자 - public static synchronized DatabaseHelper getInstance(Context context) { - if (instance == null) { - instance = new DatabaseHelper(context.getApplicationContext(), "FACEIMAGE.db", null, 1); - } - return instance; - } - // 싱글톤으로 만들기 위해 private으로 변경 - private DatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) { - super(context, name, factory, DATABASE_VERSION); - this.context = context; - Log.d(TAG, "DataBaseHelper 생성자 호출"); - } - @Override - public void onCreate(SQLiteDatabase sqLiteDatabase) { - Log.d(TAG,"Table create"); - String createQuery = "CREATE TABLE " + TABLE_NAME + - "( ID INTEGER PRIMARY KEY AUTOINCREMENT, " // 프라이머리 키 추가 - + "NAME TEXT NOT NULL, " // 사진 이름(예: person1.png, person2.jpg, ...) - + "INPUTNAME TEXT, " // 인물 이름(기본 값은 인물1, 인물2, ...) - + "PHONENUMBER TEXT, " // 인물 전화번호 - + "IMAGE BLOB," // 사진 데이터(바이트 배열) - + "HOMEDISPLAY INTEGER DEFAULT 0, " // 홈 화면 표시 여부(= 내가 좋아하는 사람 리스트) - + "THUMBNAIL_IMAGE BLOB);"; // 썸네일 이미지 데이터(바이트 배열) - sqLiteDatabase.execSQL(createQuery); - } - - @Override - public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion){ - Log.d(TAG, "Table onUpgrade"); - - if (oldVersion < 2) { // 예전 버전이 2보다 작을 때 업그레이드 로직 실행 - sqLiteDatabase.execSQL("ALTER TABLE " + TABLE_NAME + " ADD COLUMN INPUTNAME TEXT;"); - } - if (oldVersion < 3) { - sqLiteDatabase.execSQL("ALTER TABLE " + TABLE_NAME + " ADD COLUMN HOME_DISPLAY INTEGER DEFAULT 0;"); - } - if (oldVersion < 4) { - sqLiteDatabase.execSQL("ALTER TABLE " + TABLE_NAME + " ADD COLUMN THUMBNAIL_IMAGE BLOB;"); - } - } - - // 테이블의 행의 개수를 반환하는 함수 - public int getRowCount(String tableName) { - SQLiteDatabase db = this.getReadableDatabase(); - String countQuery = "SELECT * FROM " + tableName; - Cursor cursor = db.rawQuery(countQuery, null); - int rowCount = cursor.getCount(); - cursor.close(); - - return rowCount; - } - - public static Bitmap getImage(byte[] image) { // 바이트 배열을 Bitmap으로 변환하는 예시 코드 - return BitmapFactory.decodeByteArray(image, 0, image.length); - } - - // Drawable 리소스를 바이트 배열로 변환하는 메소드 - public byte[] drawableToBytes(Context context, int drawableId) throws IOException { - Resources resources = context.getResources(); - InputStream inputStream = resources.openRawResource(drawableId); - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - int size; - byte[] buffer = new byte[1024]; - while ((size = inputStream.read(buffer, 0, 1024)) >= 0) { - outputStream.write(buffer, 0, size); - } - inputStream.close(); - return outputStream.toByteArray(); - } - public boolean isNameExists(String newName) { - SQLiteDatabase db = this.getReadableDatabase(); - Cursor cursor = db.query(TABLE_NAME, new String[]{"ID"}, "INPUTNAME = ?", new String[]{newName}, null, null, null); - boolean exists = cursor.getCount() > 0; - cursor.close(); - db.close(); - return exists; - } - - //이미지 이름과 input이름이 서로 다르면 해쉬맵에 추가하여 반환함 - public Map getMismatchedImageInputNames() { - SQLiteDatabase db = this.getReadableDatabase(); - Map mismatchMap = new HashMap<>(); - - Cursor cursor = null; - try { - // 모든 행을 조회 - String query = "SELECT NAME, INPUTNAME FROM "+TABLE_NAME; - cursor = db.rawQuery(query, null); - - if (cursor.moveToFirst()) { - do { - @SuppressLint("Range") String name = cursor.getString(cursor.getColumnIndex("NAME")); - @SuppressLint("Range") String inputName = cursor.getString(cursor.getColumnIndex("INPUTNAME")); - - // IMAGE와 INPUTNAME이 다른 경우 맵에 추가 - if (!name.equals(inputName)) { - mismatchMap.put(name, inputName); - } - } while (cursor.moveToNext()); - } - } catch (SQLException e) { - e.printStackTrace(); - } finally { - if (cursor != null) { - cursor.close(); - } - db.close(); - } - return mismatchMap; - } - public boolean insertImage(String name, byte[] imageBytes) { - Log.d(TAG, "이미지 추가함"); - SQLiteDatabase db = this.getWritableDatabase(); - ContentValues values = new ContentValues(); - values.put("NAME", name); - values.put("INPUTNAME", name); // 인물 이름 기본 값 인물1, 인물2, ... - values.put("IMAGE", imageBytes); // 이미지 바이트 - values.put("PHONENUMBER", ""); // 휴대전화 번호 기본값 "" - values.put("HOMEDISPLAY", 0); // 홈 화면 표시 여부 기본값 0 - long result = db.insert(TABLE_NAME, null, values); - db.close(); // 데이터베이스 사용 후 닫기 - - return result != -1; - } - public List getImagesByName(String name) { - List images = new ArrayList<>(); - SQLiteDatabase db = this.getReadableDatabase(); - Cursor cursor = db.query(TABLE_NAME, new String[]{"IMAGE"}, "INPUTNAME = ?", new String[]{name}, null, null, null); - - if (cursor.moveToFirst()) { - do { - @SuppressLint("Range") byte[] image = cursor.getBlob(cursor.getColumnIndex("IMAGE")); - images.add(image); - } while (cursor.moveToNext()); - } - cursor.close(); - db.close(); - - return images; - } - public Map getAllImages() { - Map imagesMap = new HashMap<>(); - // 데이터베이스에서 모든 이미지 이름을 선택하는 쿼리 - String selectQuery = "SELECT " + "ID" + ", " + COLUMN_NAME + " FROM " + TABLE_NAME; - SQLiteDatabase db = this.getReadableDatabase(); - Cursor cursor = db.rawQuery(selectQuery, null); - - if (cursor.moveToFirst()) { - int idColumnIndex = cursor.getColumnIndex("ID"); // ID 컬럼 인덱스 - int nameColumnIndex = cursor.getColumnIndex(COLUMN_NAME); // 이름 컬럼 인덱스 - - do { - // 각 컬럼에서 데이터를 읽음 - int personId = cursor.getInt(idColumnIndex); - String imageName = cursor.getString(nameColumnIndex); - byte[] imageData = getImageData(personId); - - // 읽은 데이터를 HashMap에 추가 - imagesMap.put(imageName, imageData); - } while (cursor.moveToNext()); - } - - cursor.close(); - db.close(); - - // 이미지 데이터가 담긴 HashMap 반환 - return imagesMap; - } - // inputname을 통해 해당 컬럼 삭제 - public void deletePersonByName(String inputName) { - SQLiteDatabase db = this.getWritableDatabase(); - // inputname을 기준으로 해당 행을 삭제 - int result = db.delete(TABLE_NAME, "INPUTNAME = ?", new String[]{inputName}); - db.close(); // 데이터베이스 사용 후 닫기 - } - public String getPhoneNumberById(int id) { - SQLiteDatabase db = this.getReadableDatabase(); - String phoneNumber = ""; - - // 쿼리에서 ID를 기준으로 전화번호를 조회합니다. - String query = "SELECT " + COLUMN_PHONENUMBER + " FROM " + TABLE_NAME + " WHERE ID = ?"; - Cursor cursor = db.rawQuery(query, new String[]{String.valueOf(id)}); - - if (cursor.moveToFirst()) { - phoneNumber = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_PHONENUMBER)); - } - - cursor.close(); - db.close(); - - return phoneNumber; - } - public Person getPersonById(int id) { - SQLiteDatabase db = this.getReadableDatabase(); - Cursor cursor = db.rawQuery("SELECT ID, NAME, INPUTNAME, PHONENUMBER, IMAGE, HOMEDISPLAY, THUMBNAIL_IMAGE FROM " + TABLE_NAME + " WHERE ID = ?", new String[]{String.valueOf(id)}); - - if (cursor.moveToFirst()) { - @SuppressLint("Range") int personId = cursor.getInt(cursor.getColumnIndex("ID")); - @SuppressLint("Range") String name = cursor.getString(cursor.getColumnIndex("NAME")); - @SuppressLint("Range") String inputName = cursor.getString(cursor.getColumnIndex("INPUTNAME")); - @SuppressLint("Range") String phoneNumber = cursor.getString(cursor.getColumnIndex("PHONENUMBER")); - @SuppressLint("Range") byte[] image = cursor.getBlob(cursor.getColumnIndex("IMAGE")); - @SuppressLint("Range") boolean homeDisplay = cursor.getInt(cursor.getColumnIndex("HOMEDISPLAY")) == 1; - @SuppressLint("Range") byte[] thumbnailImage = cursor.getBlob(cursor.getColumnIndex("THUMBNAIL_IMAGE")); - - Person person = new Person(personId, name, image); - person.setInputName(inputName); - person.setPhone(phoneNumber); - person.setHomeDisplay(homeDisplay); - person.setThumbnailImage(thumbnailImage); - - cursor.close(); - db.close(); - return person; - } - - cursor.close(); - db.close(); - return null; - } - - // 이름이 같은 모든 사람의 썸네일 이미지를 변경하는 메서드 - public boolean updateThumbnailImageByName(String name, byte[] thumbnailImageBytes) { - SQLiteDatabase db = this.getWritableDatabase(); - ContentValues values = new ContentValues(); - values.put("THUMBNAIL_IMAGE", thumbnailImageBytes); - - int result = db.update(TABLE_NAME, values, "INPUTNAME = ?", new String[]{name}); - db.close(); - - return result != -1; - } - - // 이름이 같은 모든 사람의 인물 정보(이름, 전화 번호, 내가 좋아하는 사람 여부) 변경하는 메서드 - public boolean updatePersonByName(String oldName, String newName, String newPhoneNumber, boolean homeDisplay) { - SQLiteDatabase db = this.getWritableDatabase(); - ContentValues values = new ContentValues(); - values.put("INPUTNAME", newName); - values.put("PHONENUMBER", newPhoneNumber); - values.put("HOMEDISPLAY", homeDisplay ? 1 : 0); - - String selection = "INPUTNAME = ?"; - String[] selectionArgs = { oldName }; - - db.beginTransaction(); - try { - int result = db.update(TABLE_NAME, values, selection, selectionArgs); - db.setTransactionSuccessful(); - return result > 0; - } catch (Exception e) { - e.printStackTrace(); - return false; - } finally { - db.endTransaction(); - db.close(); - } - } - // 전화 기록을 가져오는 메서드 - @SuppressLint("Range") - private Map getCallDurations() { - Map callLogDuration = new HashMap<>(); - Cursor cursor = context.getContentResolver().query(CallLog.Calls.CONTENT_URI, null, null, null, null); - - if (cursor != null) { - while (cursor.moveToNext()) { - String number = cursor.getString(cursor.getColumnIndex(CallLog.Calls.NUMBER)); - long duration = cursor.getLong(cursor.getColumnIndex(CallLog.Calls.DURATION)); - number = normalizePhoneNumber(number); - callLogDuration.put(number, callLogDuration.getOrDefault(number, 0L) + duration); - } - cursor.close(); - } - return callLogDuration; - } - - // 전화번호 정규화 메서드 - private String normalizePhoneNumber(String phoneNumber) { - return phoneNumber.replaceAll("[^0-9]", ""); - } - public List getUniquePersons() { - List people = new ArrayList<>(); - SQLiteDatabase db = this.getReadableDatabase(); - Map callDurations = getCallDurations(); // 전화 기록 가져오기 - - Cursor cursor = db.rawQuery("SELECT ID, NAME, INPUTNAME, PHONENUMBER, IMAGE, HOMEDISPLAY, THUMBNAIL_IMAGE FROM " + TABLE_NAME, null); - - if (cursor.moveToFirst()) { - Set uniqueNames = new HashSet<>(); - do { - @SuppressLint("Range") int id = cursor.getInt(cursor.getColumnIndex("ID")); - @SuppressLint("Range") String name = cursor.getString(cursor.getColumnIndex("NAME")); - @SuppressLint("Range") String inputName = cursor.getString(cursor.getColumnIndex("INPUTNAME")); - @SuppressLint("Range") String phoneNumber = cursor.getString(cursor.getColumnIndex("PHONENUMBER")); - @SuppressLint("Range") byte[] image = cursor.getBlob(cursor.getColumnIndex("IMAGE")); - @SuppressLint("Range") boolean homeDisplay = cursor.getInt(cursor.getColumnIndex("HOMEDISPLAY")) == 1; - @SuppressLint("Range") byte[] thumbnailImage = cursor.getBlob(cursor.getColumnIndex("THUMBNAIL_IMAGE")); - - - // 중복된 이름 제거 - if (uniqueNames.add(inputName)) { - Person person = new Person(id, name, image); - person.setInputName(inputName); - person.setPhone(phoneNumber); - person.setHomeDisplay(homeDisplay); - person.setThumbnailImage(thumbnailImage); - - // 통화량 설정 - long totalDuration = callDurations.getOrDefault(phoneNumber, 0L); - person.setTotalDuration(totalDuration); - - people.add(person); - } - } while (cursor.moveToNext()); - } - cursor.close(); - db.close(); - - return people; - } - - - public boolean getHomeDisplayById(int id) { - SQLiteDatabase db = this.getReadableDatabase(); - boolean homeDisplay = false; - - String query = "SELECT HOMEDISPLAY FROM " + TABLE_NAME + " WHERE ID = ?"; - Cursor cursor = db.rawQuery(query, new String[]{String.valueOf(id)}); - - if (cursor.moveToFirst()) { - homeDisplay = cursor.getInt(cursor.getColumnIndexOrThrow("HOMEDISPLAY")) == 1; - } - - cursor.close(); - db.close(); - - return homeDisplay; - } - - private byte[] getImageData(int personId) { - SQLiteDatabase db = this.getReadableDatabase(); - SQLiteStatement statement = db.compileStatement("SELECT IMAGE FROM " + TABLE_NAME + " WHERE ID = ?"); - statement.bindLong(1, personId); - byte[] buffer = new byte[2048]; // 버퍼 사이즈 설정 - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - - try { - ParcelFileDescriptor pfd = statement.simpleQueryForBlobFileDescriptor(); - if (pfd != null) { - FileInputStream inputStream = new FileInputStream(pfd.getFileDescriptor()); - int bytesRead; - while ((bytesRead = inputStream.read(buffer)) != -1) { - outputStream.write(buffer, 0, bytesRead); - } - inputStream.close(); - pfd.close(); - } - return outputStream.toByteArray(); - } catch (IOException e) { - e.printStackTrace(); - return null; - } finally { - try { - outputStream.close(); - } catch (IOException e) { - e.printStackTrace(); - } - statement.close(); - } - } - @SuppressLint("Range") - public String getInputNameByImageName(String imageName) { - SQLiteDatabase db = this.getReadableDatabase(); - String inputName = null; - - Cursor cursor = db.query(TABLE_NAME, new String[]{COLUMN_INPUTNAME}, "NAME = ?", new String[]{imageName}, null, null, null); - - if (cursor.moveToFirst()) { - inputName = cursor.getString(cursor.getColumnIndex(COLUMN_INPUTNAME)); - } - cursor.close(); - db.close(); - return inputName; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/metasearch/data/model/Choice.java b/app/src/main/java/com/example/metasearch/data/model/Choice.java deleted file mode 100644 index c964e4ce..00000000 --- a/app/src/main/java/com/example/metasearch/data/model/Choice.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.example.metasearch.data.model; - -public class Choice { - private Message message; - - public Message getMessage() { - return message; - } - - public void setMessage(Message message) { - this.message = message; - } -} diff --git a/app/src/main/java/com/example/metasearch/data/model/Circle.java b/app/src/main/java/com/example/metasearch/data/model/Circle.java deleted file mode 100644 index 2a526aa6..00000000 --- a/app/src/main/java/com/example/metasearch/data/model/Circle.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.example.metasearch.data.model; - -public class Circle { - private float centerX; - private float centerY; - private float radius; - - public Circle(float centerX, float centerY, float radius) { - this.centerX = centerX; - this.centerY = centerY; - this.radius = radius; - } - - public float getCenterX() { - return centerX; - } - - public float getCenterY() { - return centerY; - } - - public float getRadius() { - return radius; - } - - // Setters omitted for brevity -} diff --git a/app/src/main/java/com/example/metasearch/data/model/Message.java b/app/src/main/java/com/example/metasearch/data/model/Message.java deleted file mode 100644 index f3a323ad..00000000 --- a/app/src/main/java/com/example/metasearch/data/model/Message.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.example.metasearch.data.model; - -public class Message { - private String role; - private String content; - - public Message(String role, String content) { - this.role = role; - this.content = content; - } - - public String getRole() { - return role; - } - - public void setRole(String role) { - this.role = role; - } - - public String getContent() { - return content; - } - - public void setContent(String content) { - this.content = content; - } -} diff --git a/app/src/main/java/com/example/metasearch/data/model/Person.java b/app/src/main/java/com/example/metasearch/data/model/Person.java deleted file mode 100644 index fcd11039..00000000 --- a/app/src/main/java/com/example/metasearch/data/model/Person.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.example.metasearch.data.model; - -public class Person { - private int id; // 고유 ID - private String imageName; // 사진 이름 - private String inputName; // 사용자가 입력한(수정한) 인물 이름 - private byte[] image; // 홈 화면 상단에 작게 표시되는 얼굴 이미지를 위한 데이터 - private String phone; // 인물의 전화번호. 기본값은 "". - private long totalDuration; // 총 통화 시간 - private boolean homeDisplay; // 홈 화면에 표시할지 여부 - - private double normalizedScore; // 정규화 점수 - - private int photoCount; // 사진 개수 - private byte[] thumbnailImage; - public Person(int id, String imageName, byte[] image) { - this.id = id; - this.imageName = imageName; - this.image = image; - this.inputName = ""; - this.phone = ""; - this.homeDisplay = false; // 기본값은 false로 설정 - - this.photoCount = 0; // 기본값 설정 - } - public int getId() { return id; } - public void setId(int id) { this.id = id; } - public String getInputName() { - return inputName; - } - public void setInputName(String inputName) { - this.inputName = inputName; - } - public String getImageName() { return imageName; } - public byte[] getImage() { - return image; - } - public String getPhone() { return phone; } - public void setImageName(String imageName) { - this.imageName = imageName; - } - public void setImage(byte[] image) { - this.image = image; - } - public void setPhone(String phone) { - this.phone = phone != null ? phone : ""; - } - public long getTotalDuration() { - return totalDuration; - } - public void setTotalDuration(long totalDuration) { - this.totalDuration = totalDuration; - } - - public boolean isHomeDisplay() { - return homeDisplay; - } - public void setHomeDisplay(boolean homeDisplay) { - this.homeDisplay = homeDisplay; - } - public int getPhotoCount() { - return photoCount; - } - public void setPhotoCount(int photoCount) { - this.photoCount = photoCount; - } - public double getNormalizedScore() { - return normalizedScore; - } - - public void setNormalizedScore(double normalizedScore) { - this.normalizedScore = normalizedScore; - } - public byte[] getThumbnailImage() { - return thumbnailImage; - } - public void setThumbnailImage(byte[] thumbnailImage) { - this.thumbnailImage = thumbnailImage; - } -} diff --git a/app/src/main/java/com/example/metasearch/manager/AIRequestManager.java b/app/src/main/java/com/example/metasearch/manager/AIRequestManager.java deleted file mode 100644 index 7a82ae15..00000000 --- a/app/src/main/java/com/example/metasearch/manager/AIRequestManager.java +++ /dev/null @@ -1,414 +0,0 @@ -package com.example.metasearch.manager; - -import static androidx.constraintlayout.helper.widget.MotionEffect.TAG; - -import android.content.Context; -import android.net.Uri; -import android.util.Base64; -import android.util.Log; - -import com.example.metasearch.data.dao.AnalyzedImageListDatabaseHelper; -import com.example.metasearch.data.dao.DatabaseHelper; -import com.example.metasearch.utils.HttpHelper; -import com.example.metasearch.network.interfaces.CircleDataUploadCallbacks; -import com.example.metasearch.data.model.Circle; -import com.example.metasearch.network.response.CircleDetectionResponse; -import com.example.metasearch.network.response.UploadResponse; -import com.example.metasearch.network.api.ApiService; -import com.example.metasearch.utils.UriToFileConverter; -import com.google.gson.Gson; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; - -import okhttp3.MediaType; -import okhttp3.MultipartBody; -import okhttp3.RequestBody; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -public class AIRequestManager { - private static final String AIserver_BASE_URL = "http://113.198.85.5"; // ai 서버의 기본 url - private static AIRequestManager aiImageUploader; - private ImageAnalyzeListManager imageAnalyzeListManager; - private AnalyzedImageListDatabaseHelper analyzedImageListDatabaseHelper; - - private ApiService aiService; - static final String TABLE_NAME = "Faces"; - private Context context; - - private AIRequestManager(Context context){ - //this.aiService = AIHttpService.getInstance(AIserver_BASE_URL); - this.aiService = HttpHelper.getInstance(AIserver_BASE_URL).getRetrofit().create(ApiService.class); - this.analyzedImageListDatabaseHelper = AnalyzedImageListDatabaseHelper.getInstance(context); - - this.imageAnalyzeListManager = ImageAnalyzeListManager.getInstance(context); - this.context = context; - - } - - public ApiService getAiService(){ - return aiService; - } - - public static AIRequestManager getAiImageUploader(Context context){ - Log.d(TAG,"AIImageUploaderController 함수 들어옴 객체 생성 or 반환"); - if(aiImageUploader == null){ - aiImageUploader = new AIRequestManager(context); - - } - return aiImageUploader; - } - - // 이미지 분석을 시작한다는 첫 번째 요청 - // 서버에서 해당 db의 faces 폴더를 삭제해도록 함 - public CompletableFuture firstUploadImage(String DBName){ - Log.d(TAG,"첫 번째 업로드"); - CompletableFuture future = new CompletableFuture<>(); - - RequestBody firstBody = RequestBody.create(MediaType.parse("text/plain"),"first"); //DB이름과 어디에 저장되어야하는지에 관한 정보를 전달 - RequestBody sourceBody = RequestBody.create(MediaType.parse("text/plain"),DBName); //DB이름과 어디에 저장되어야하는지에 관한 정보를 전달 - - Call call = aiService.upload_first(firstBody,sourceBody); //이미지 업로드 API 호출 - call.enqueue(new Callback() { //비동기 - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { - Log.e(TAG, "first upload 성공: " + response.message()); - future.complete(null); - } - } - @Override - public void onFailure(Call call, Throwable t) { - Log.e(TAG, "first upload 실패함" + t.getMessage()); - future.complete(null); - } - }); - return future; - } - - // 이미지 분석이 완료되었다는 마지막 요청 - public CompletableFuture completeUploadImage(DatabaseHelper databaseHelper,String DBName){ - CompletableFuture future = new CompletableFuture<>(); - - int index = databaseHelper.getRowCount(TABLE_NAME); - - - //이미지 출처 정보를 전송할 RequestBody 생성 - RequestBody finishBody = RequestBody.create(MediaType.parse("text/plain"),"finish"); //DB이름과 어디에 저장되어야하는지에 관한 정보를 전달 - RequestBody sourceBody = RequestBody.create(MediaType.parse("text/plain"),DBName); //DB이름과 어디에 저장되어야하는지에 관한 정보를 전달 - RequestBody indexBody = RequestBody.create(MediaType.parse("text/plain"), String.valueOf(index)); //DB이름과 어디에 저장되어야하는지에 관한 정보를 전달 - - - Call call = aiService.upload_finish(finishBody,sourceBody,indexBody); //이미지 업로드 API 호출 - - call.enqueue(new Callback() { //비동기 - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { - Log.e(TAG, "마지막 요청 성공: " + response.message()); - if(response.body() != null) { - for (UploadResponse.ImageData imageData : response.body().getImages()) { - boolean isFaceExit = imageData.getIsExit(); //추출된 이미지가 있다는 것 - if (isFaceExit) { - //Log.d(TAG,"추출한 얼굴 이미지 있음"); - Log.d(TAG, "추가 이미지에 대한 응답 받음 / 추가할 얼굴 있음"); - String imageName = imageData.getImageName(); - String imageBytes = imageData.getImageBytes(); - // 여기서 바이트 배열로 변환 후 이미지 처리를 할 수 있음 - byte[] decodedString = Base64.decode(imageBytes, Base64.DEFAULT); - Log.d(TAG, "imageName : " + imageName); - databaseHelper.insertImage(imageName, decodedString); - } else { - Log.d(TAG, "추가 이미지에 대한 응답 받음 / 추가할 얼굴 없음"); - //Log.e(TAG, "이미지 업로드 성공, 추출된 이미지 없음" + response.message()); - } - } - } - future.complete(null); - - } - } - @Override - public void onFailure(Call call, Throwable t) { - Log.e(TAG, "마지막 요청 실패" + t.getMessage()); - future.complete(null); - - } - }); - return future; - } - - //추가된 이미지 관해 서버에 전송 - public CompletableFuture uploadAddGalleryImage( ArrayList imagePaths,String source) throws IOException { - ImageNotificationManager imageNotificationManager = ImageNotificationManager.getImageNotification(context); - List> futuresList = new ArrayList<>(); - Log.d(TAG,"uploadAddGalleryImage 안에 들어옴"); - //List> futuresList = new ArrayList<>(); - //Log.d(TAG,"addImge 이름 : " + imageFile.getName()); - RequestBody requestBody; - MultipartBody.Part imagePart; - for(String addImagePath : imagePaths){ - CompletableFuture future = new CompletableFuture<>(); - futuresList.add(future); - - File imageFile = new File(addImagePath); - - //추가 이미지 분석이 필요한 이미지를 전달 - requestBody = RequestBody.create(MediaType.parse("image"),imageFile); - //파일의 경로를 해당 이미지의 이름으로 설정함 - imagePart = MultipartBody.Part.createFormData("addImage",imageFile.getName(),requestBody); - - - //이미지 출처 정보를 전송할 RequestBody 생성 - RequestBody sourceBody = RequestBody.create(MediaType.parse("text/plain"),source); //DB이름과 어디에 저장되어야하는지에 관한 정보를 전달 - //API 호출 - Call call = aiService.uploadAddImage(imagePart,sourceBody); //이미지 업로드 API 호출 - call.enqueue(new Callback() { //비동기 - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { - Log.e(TAG, "AI 서버 Image upload 성공: " + response.message()); - // updateProgress(imageNotificationManager,imagePaths.size() ); - future.complete(null); - } - } - @Override - public void onFailure(Call call, Throwable t) { - if (t instanceof IOException) { - // 네트워크 관련 예외 처리 (예: timeout) - Log.e(TAG, "네트워크 문제 또는 서버에서 timeout 발생: " + t.getMessage()); - Log.d(TAG, "오류로 인해 분석 취소되는 이미지 이름 : " + imageFile.getName()); - //해당 이미지 처리 x로 설정 - imageAnalyzeListManager.delete_fail_image_analyze(imageFile.getName()); - } else { - // 그 외 예외 처리 - Log.e(TAG, "알 수 없는 예외 발생: " + t.getMessage()); - Log.d(TAG, "오류로 인해 분석 취소되는 이미지 이름 : " + imageFile.getName()); - imageAnalyzeListManager.delete_fail_image_analyze(imageFile.getName()); - - } - future.complete(null); // 예외를 future에 설정하여 예외 상황을 알림 - } -// Log.e(TAG, "추가 이미지 업로드 실패함" + t.getMessage()); -// //future.completeExceptionally(t); // 작업 실패 시 future에 예외를 설정 - }); - } - return CompletableFuture.allOf(futuresList.toArray(new CompletableFuture[0])); - } -// public CompletableFuture> uploadAddGalleryImage(ArrayList imagePaths, String source) { -// Log.d(TAG, "uploadAddGalleryImage 안에 들어옴"); -// //알림 매니저 -// -// -// // 첫 번째 Future를 null로 처리하여 체인을 시작합니다. -// CompletableFuture initialFuture = CompletableFuture.completedFuture(null); -// List results = new ArrayList<>(); -// -// // 이전 작업이 완료된 후 다음 작업을 시작하는 체인을 만듭니다. -// CompletableFuture chain = initialFuture; -// int size = imagePaths.size(); -// for (String imagePath : imagePaths) { -// chain = chain.thenCompose(aVoid -> uploadSingleImage(imagePath, source,size).thenApply(result -> { -// results.add(result); -// return result; -// })); -// -// } -// -// // 모든 작업이 완료되면 결과 리스트를 반환합니다. -// return chain.thenApply(v -> results); -// } -// private CompletableFuture uploadSingleImage(String imagePath, String source, int imageSize) { -// ImageNotificationManager imageNotificationManager = ImageNotificationManager.getImageNotification(context); -// CompletableFuture future = new CompletableFuture<>(); -// File imageFile = new File(imagePath); -// -// RequestBody requestBody = RequestBody.create(MediaType.parse("image/*"), imageFile); -// MultipartBody.Part imagePart = MultipartBody.Part.createFormData("addImage", imageFile.getName(), requestBody); -// RequestBody sourceBody = RequestBody.create(MediaType.parse("text/plain"), source); -// -// Call call = aiService.uploadAddImage(imagePart, sourceBody); -// call.enqueue(new Callback() { -// @Override -// public void onResponse(Call call, Response response) { -// if (response.isSuccessful()) { -// Log.d(TAG, "AI 서버 이미지 업로드 완료 "); -// future.complete("Success: " + imagePath); -// -// // 업로드 성공 후에 진행률 업데이트 -// updateProgress(imageNotificationManager, imageSize); -// } else { -// future.complete("Failed: " + imagePath); -// //분석된 리스트에 삭제함 즉, DB에서 삭제함 -// analyzedImageListDatabaseHelper.removeImagePath(imagePath); -// } -// } -// -// @Override -// public void onFailure(Call call, Throwable t) { -// future.complete("Failed: " + imagePath); -// analyzedImageListDatabaseHelper.removeImagePath(imagePath); -// -// } -// }); -// -// return future; -// } - -// private void updateProgress(ImageNotificationManager manager, int imageSize) { -// if (imageSize > 0) { // imageSize가 0보다 클 때만 진행률 계산 -// int progressCurrent = manager.getProgressCurrent(); -// int increase = Math.round(100.0f / imageSize); -// manager.setProgressCurrent(progressCurrent + increase); -// Log.d(TAG, "현재 프로그래스 숫자 : " + progressCurrent); -// Log.d(TAG, "increase : " + increase); -// } else { -// Log.e(TAG, "imageSize is zero, cannot update progress."); -// } -// } - private void updateProgress(ImageNotificationManager manager, int imageSize) { - if (imageSize > 0) { - int progressCurrent = manager.getProgressCurrent(); //현재 진행량 - manager.setProgressCurrent(progressCurrent + +1); - Log.d(TAG, "현재 프로그래스 숫자 : " + (progressCurrent + +1)); - Log.d(TAG, "increase : " + progressCurrent + +1); - } else { - Log.e(TAG, "imageSize is zero, cannot update progress."); - } - } - - - - - //삭제된 이미지 관해 서버에 전송 - public CompletableFuture uploadDeleteGalleryImage(DatabaseHelper databaseHelper, ArrayListdeleteImagePaths,String source){ - List> futuresList = new ArrayList<>(); - - RequestBody requestBody; - MultipartBody.Part imagePart; - - for(String deleteImagePath : deleteImagePaths){ - CompletableFuture future = new CompletableFuture<>(); - futuresList.add(future); - requestBody = RequestBody.create(MediaType.parse("filename"),deleteImagePath); - //파일의 경로를 해당 이미지의 이름으로 설정함 - imagePart = MultipartBody.Part.createFormData("deleteImage",deleteImagePath,requestBody); - - //이미지 출처 정보를 전송할 RequestBody 생성 - RequestBody sourceBody = RequestBody.create(MediaType.parse("text/plain"),source); //DB이름과 어디에 저장되어야하는지에 관한 정보를 전달 - // RequestBody endIndicatorBody = RequestBody.create(MediaType.parse("text/plain"), String.valueOf(isFinal)); //갤러리의 마지막 요청인지에 대한 정보 전달 - - //API 호출 - Call call = aiService.UploadDeleteImage(imagePart,sourceBody); //이미지 업로드 API 호출 - call.enqueue(new Callback() { //비동기 - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { - Log.e(TAG, "Image delete 성공: " + response.message()); - future.complete(null); - } - } - @Override - public void onFailure(Call call, Throwable t) { - Log.e(TAG, "삭제 이미지 업로드 실패함" + t.getMessage()); - future.complete(null); - - } - }); - } - - return CompletableFuture.allOf(futuresList.toArray(new CompletableFuture[0])); - - } - - //Database 이미지 서버에 전송 - public CompletableFuture uploadDBImage(Map imagesList, String dbName){ - //데이터베이스 서버 요청이 다 끝나면, 다 끝났다는 것을 반환함 - Log.d(TAG,"uploadDBImage 들어옴"); - // 모든 비동기 작업을 추적하기 위한 CompletableFuture 리스트 생성 - List> futuresList = new ArrayList<>(); - for (Map.Entry database_image_element : imagesList.entrySet()){ - CompletableFuture future = new CompletableFuture<>(); - futuresList.add(future); - //byte[]에서 RequestBody 생성 - RequestBody requestBody = RequestBody.create(MediaType.parse("image/jpeg"),database_image_element.getValue()); - MultipartBody.Part imagePart = MultipartBody.Part.createFormData("faceImage",database_image_element.getKey(),requestBody); - - //이미지 출처 정보를 전송할 RequestBody 생성 - RequestBody sourceBody = RequestBody.create(MediaType.parse("text/plain"),dbName); - - //API 호출 - Call call = aiService.uploadDatabaseImage(imagePart,sourceBody); //이미지 업로드 API 호출 - call.enqueue(new Callback() { //비동기 - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { - Log.e(TAG, "데이터베이스 업로드 성공함" + response.message()); - future.complete(null); // 작업 성공 시 future를 완료 상태로 설정 - } - - } - @Override - public void onFailure(Call call, Throwable t) { - Log.e(TAG, "데이터베이스 업로드 실패함" + t.getMessage()); - future.completeExceptionally(t); // 작업 실패 시 future에 예외를 설정 - } - }); - } - // 모든 futures가 완료될 때까지 기다림 - return CompletableFuture.allOf(futuresList.toArray(new CompletableFuture[0])); - } - - public void deletePerson(String dbName, String imageName) { -// DeleteEntityRequest request = new DeleteEntityRequest(dbName, imageName); -// Call call = aiService.deletePerson(request); - RequestBody db = RequestBody.create(MediaType.parse("text/plain"),dbName); - RequestBody name = RequestBody.create(MediaType.parse("text/plain"),imageName); - Call call = aiService.deletePerson(db, name); - call.enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - Log.d("DEL_PERSON", response.message()); - } - @Override - public void onFailure(Call call, Throwable t) { - Log.d("DEL_PERSON", "DELETE PERSON FAILED"); - } - }); - } - - - // AI Server로 이미지와 원 리스트 전송 - public void uploadCircleData(Uri imageUri, List circles, String source, Context context, CircleDataUploadCallbacks callbacks) throws IOException { - File file = UriToFileConverter.getFileFromUri(context, imageUri); - RequestBody requestFile = RequestBody.create(MediaType.parse("image/jpeg"), file); - MultipartBody.Part body = MultipartBody.Part.createFormData("searchImage", file.getName(), requestFile); - - Gson gson = new Gson(); - String jsonCircles = gson.toJson(circles); - RequestBody circleData = RequestBody.create(MediaType.parse("application/json"), jsonCircles); - RequestBody sourceBody = RequestBody.create(MediaType.parse("text/plain"), source); - - Call call = aiService.uploadImageAndCircles(body, sourceBody, circleData); - call.enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { - callbacks.onCircleUploadSuccess(response.body().getDetectedObjects()); - } else { - callbacks.onCircleUploadFailure("Server responded with error"); - } - } - @Override - public void onFailure(Call call, Throwable t) { - callbacks.onCircleUploadFailure("Failed to upload data and image: " + t.getMessage()); - } - }); - } -} diff --git a/app/src/main/java/com/example/metasearch/manager/ChatGPTManager.java b/app/src/main/java/com/example/metasearch/manager/ChatGPTManager.java deleted file mode 100644 index 61d60e95..00000000 --- a/app/src/main/java/com/example/metasearch/manager/ChatGPTManager.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.example.metasearch.manager; - -import com.example.metasearch.BuildConfig; -import com.example.metasearch.utils.HttpHelper; -import com.example.metasearch.data.model.Message; -import com.example.metasearch.network.request.OpenAIRequest; -import com.example.metasearch.network.response.OpenAIResponse; -import com.example.metasearch.network.api.ApiService; - -import java.util.List; - -import retrofit2.Callback; - -public class ChatGPTManager { - private static ChatGPTManager instance; - private static final String BASE_URL = "https://api.openai.com/v1/"; - private static final String API_KEY = BuildConfig.OPENAI_API_KEY; // API 키 설정 - private ApiService apiService; - - // 생성자를 private으로 설정하여 외부에서 인스턴스 생성을 막음 - private ChatGPTManager() { - this.apiService = HttpHelper.getInstance(BASE_URL).create(ApiService.class); - } - - // 싱글톤 인스턴스를 반환하는 메서드 - public static synchronized ChatGPTManager getInstance() { - if (instance == null) { - instance = new ChatGPTManager(); - } - return instance; - } - - public void getChatResponse(List prompt, int maxTokens, Callback callback) { - String authToken = "Bearer " + API_KEY; - OpenAIRequest chatRequest = new OpenAIRequest("gpt-3.5-turbo", prompt); - apiService.createChatCompletion(authToken, chatRequest).enqueue(callback); - } - - -} diff --git a/app/src/main/java/com/example/metasearch/manager/ImageAnalysisWorker.java b/app/src/main/java/com/example/metasearch/manager/ImageAnalysisWorker.java deleted file mode 100644 index abf96ea8..00000000 --- a/app/src/main/java/com/example/metasearch/manager/ImageAnalysisWorker.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.example.metasearch.manager; - -import android.content.Context; - -import androidx.annotation.NonNull; -import androidx.work.Worker; -import androidx.work.WorkerParameters; - -import com.example.metasearch.data.dao.DatabaseHelper; - -public class ImageAnalysisWorker extends Worker { - private ImageServiceRequestManager imageServiceRequestManager; - - public ImageAnalysisWorker( - @NonNull Context context, - @NonNull WorkerParameters params) { - super(context, params); - DatabaseHelper databaseHelper = DatabaseHelper.getInstance(context); - this.imageServiceRequestManager = ImageServiceRequestManager.getInstance(context,databaseHelper); - } - - @NonNull - @Override - public Result doWork() { - // 이미지 분석 시작 - try { - imageServiceRequestManager.getImagePathsAndUpload(); - return Result.success(); - } catch (Exception e) { - return Result.failure(); - } - } -} - diff --git a/app/src/main/java/com/example/metasearch/manager/ImageAnalyzeListManager.java b/app/src/main/java/com/example/metasearch/manager/ImageAnalyzeListManager.java deleted file mode 100644 index 2cc68f16..00000000 --- a/app/src/main/java/com/example/metasearch/manager/ImageAnalyzeListManager.java +++ /dev/null @@ -1,162 +0,0 @@ -package com.example.metasearch.manager; - -import static androidx.constraintlayout.helper.widget.MotionEffect.TAG; - -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.util.Log; - -import com.example.metasearch.data.dao.AnalyzedImageListDatabaseHelper; - -import java.util.ArrayList; -import java.util.Iterator; - -public class ImageAnalyzeListManager { - private ArrayList analyzed_image_list; //분석된 이미지 리스트 - private ArrayList addImagePaths; //추가되는 이미지에 대한 경로를 저장하는 리스트 - private ArrayList deleteImagePaths; //삭제되는 이미지에 대한 경로를 저장하는 리스트 - private AnalyzedImageListDatabaseHelper analyzedImageListDatabaseHelper; - private SQLiteDatabase database; - private static final String TABLE_NAME = "analyzed_images"; - private static final String COLUMN_IMAGE_PATH = "image_path"; - - - //싱글톤 객체 - private static ImageAnalyzeListManager imageAnalyzeListObject; - private ImageAnalyzeListManager(Context context){ // 싱글톤 - analyzedImageListDatabaseHelper = AnalyzedImageListDatabaseHelper.getInstance(context); - database = analyzedImageListDatabaseHelper.getWritableDatabase(); - //loadAnalyzedImages(); -// Log.d("analyzed_image_list", analyzed_image_list.toString()); - addImagePaths = new ArrayList<>(); //추가 이미지 경로를 저장하는 리스트 - deleteImagePaths = new ArrayList<>(); //삭제 이미지 경로를 저장하는 리스트 - } - - public static ImageAnalyzeListManager getInstance(Context context){ //객체 생성(싱글톤 구현) - if(imageAnalyzeListObject == null){ - imageAnalyzeListObject = new ImageAnalyzeListManager(context); - } - return imageAnalyzeListObject; - } - - //데이터베이스에 있는 리스트 가져옴 -// private ArrayList loadAnalyzedImages() { -// Cursor cursor = database.query(TABLE_NAME, new String[]{COLUMN_IMAGE_PATH}, null, null, null, null, null); -// analyzed_image_list = new ArrayList<>(); -// while (cursor.moveToNext()) { -// analyzed_image_list.add(cursor.getString(0)); -// } -// cursor.close(); -// return analyzed_image_list; -// } - private ArrayList loadAnalyzedImages() { - Cursor cursor = null; - try { - cursor = database.query(TABLE_NAME, new String[]{COLUMN_IMAGE_PATH}, null, null, null, null, null); - if (cursor == null) { - return new ArrayList<>(); // 또는 적절한 예외 처리 - } - analyzed_image_list = new ArrayList<>(); - while (cursor.moveToNext()) { - analyzed_image_list.add(cursor.getString(0)); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - return analyzed_image_list; - } - - - // analyzed_image_list 추가 & addImagePaths에 추가 & db에 추가 - public ArrayList addImagePath(String imagePath,ArrayList analyzed_image_list) { - analyzed_image_list.add(imagePath); //analyzed_image_list에 추가 - addImagePaths.add(imagePath); //addImagePaths에 추가 - Log.d(TAG, "Added image path: " + imagePath); - // Update database - analyzedImageListDatabaseHelper.addImagePath(imagePath); //db에 추가 - - - return addImagePaths; - } - - //deleteImagePaths에 추가 & db에서 삭제 - public ArrayList removeImagePath(String imagePath) { -// if (analyzed_image_list.contains(imagePath)) { - deleteImagePaths.add(imagePath); - Log.d(TAG, "Removed image path: " + imagePath); - analyzedImageListDatabaseHelper.removeImagePath(imagePath); //db에서 해당 이미지 경로 삭제 - - return deleteImagePaths; - } - - - //추가, 삭제 정보 가지고 있는 리스트 정보 삭제 - public void clearAddDeleteImageList(){ - Log.d(TAG,"삭제 전 추가 이미지 리스트 사이즈 :"+addImagePaths.size()); - addImagePaths.clear(); - deleteImagePaths.clear(); - Log.d(TAG,"삭제 후 추가 이미지 리스트 사이즈 : "+addImagePaths.size()); - } - - //삭제해야하는 이미지 경로 리스트 반환 - public ArrayList checkDeleteImagePath(ArrayList imagesPaths){ - Log.d(TAG,"checkDeleteImagePath 함수에 들어옴"); - - //데이타베이스에 있는 리스트 가져옴 - //analyzed_image_list = loadAnalyzedImages(); - analyzed_image_list = analyzedImageListDatabaseHelper.loadAnalyzedImages(); - Log.d(TAG,"analyzed_image_list 사이즈 : "+analyzed_image_list.size()); - - - Iterator iterator = analyzed_image_list.iterator(); - while (iterator.hasNext()) { - String analyze_image_path = iterator.next(); - if(!imagesPaths.contains(analyze_image_path)) { //갤러리 상에 존재하지 않는 이미지 경로라면 - Log.d(TAG,"삭제할 이미지 찾음"); - //compare_image_list.put(analyze_image_path, "delete"); //삭제해야하는 이미지라는 것을 표시 -// deleteImagePaths.add(analyze_image_path); //삭제해야하는 이미지 경로 리스트에 추가 - deleteImagePaths = removeImagePath(analyze_image_path); //deleteImagePaths에 추가 및 db에서 해당 데이터 삭제 - iterator.remove(); //이미지 분석 리스트에서 해당 이미지 경로 요소 삭제 - } - } - return deleteImagePaths; - } - - //추가 분석할 이미지가 있는지 찾음 - public ArrayList checkAddImagePath(ArrayList imagesPaths){ - Log.d(TAG,"checkAddImagePath 함수에 들어옴"); - //데이타베이스에 있는 리스트 가져옴 - //analyzed_image_list = loadAnalyzedImages(); - analyzed_image_list = analyzedImageListDatabaseHelper.loadAnalyzedImages(); - Log.d(TAG,"analyzed_image_list 사이즈 : "+analyzed_image_list.size()); - - for (String imagePath : imagesPaths) { //이미지 경로를 하나씩 가져와서 갤러리 이미지 업로드 함수를 호출 - Log.d(TAG,"imagePath : "+imagePath); //-> ex) /storage/emulated/0/DCIM/20240322_200556.jpg - // 이미지 경로의 확장자 확인 //png,jpg,jpeg면 추가 이미지로 처리함 - if (imagePath.toLowerCase().endsWith(".png") || - imagePath.toLowerCase().endsWith(".jpg") || - imagePath.toLowerCase().endsWith(".jpeg")) { - - if(!analyzed_image_list.contains(imagePath)){ //이미지 분석에서 해당 이미지 경로가 없다면 추가 - //analyze_image_list에 추가 & addImagePaths에 추가 & db에 추가 - addImagePaths = addImagePath(imagePath,analyzed_image_list); - Log.d(TAG, "추가함 imagePath : " + imagePath); - } - } - } - return addImagePaths; - } - - //네트워크 에러로 인해 분석되지 않은 이미지 경로를 분석된 이미지 리스트에서 삭재하는 함수 - public void delete_fail_image_analyze(String imagePath){ - //해당 이미지는 분석되지 않았다고 처리함 - analyzed_image_list.remove(imagePath); - //database에서도 삭제함 - analyzedImageListDatabaseHelper.removeImagePath(imagePath); - - } - -} diff --git a/app/src/main/java/com/example/metasearch/manager/ImageDialogManager.java b/app/src/main/java/com/example/metasearch/manager/ImageDialogManager.java deleted file mode 100644 index 04a55de3..00000000 --- a/app/src/main/java/com/example/metasearch/manager/ImageDialogManager.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.example.metasearch.manager; - -import android.app.Activity; -import android.app.Dialog; -import android.content.Context; -import android.graphics.Color; -import android.graphics.drawable.ColorDrawable; -import android.view.View; -import android.widget.Button; -import android.widget.TextView; - -import com.example.metasearch.R; - -//이미지 분석/ 삭제 다이얼로그를 띄우는 것 -public class ImageDialogManager { - private Dialog image_dialog; //이미지 다이얼로그 - private Dialog no_image_dialog; //분석할 이미지가 없으면 뜨는 다이얼로그 - private static ImageDialogManager instance; - - private ImageDialogManager(Context context){ - image_dialog = new Dialog(context); - image_dialog.setContentView(R.layout.image_analyze_dialog); - image_dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); - Button imageDialogOkBtn = image_dialog.findViewById(R.id.btnDialogOk); - //이미지 다이얼로그 버튼을 클릭하면 다이얼로그를 닫는다. - imageDialogOkBtn.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - image_dialog.dismiss(); - } - }); - - no_image_dialog = new Dialog(context); - no_image_dialog.setContentView(R.layout.no_image_analyze_dialog); - no_image_dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); - Button noimageDialogOkBtn = no_image_dialog.findViewById(R.id.btnDialogOk); - noimageDialogOkBtn.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - no_image_dialog.dismiss(); - } - }); - - } - public static ImageDialogManager getImageDialogManager(Context context){ - if (instance == null){ - instance = new ImageDialogManager(context); - } - return instance; - } - - Dialog getImage_dialog(boolean is_add){ // 알림 반환 - if(!is_add){ //추가 분석이 아니라면 - TextView image_dialog_title = image_dialog.findViewById(R.id.dialogTitle); - image_dialog_title.setText(R.string.title_text_delete); - TextView image_dialog_text = image_dialog.findViewById(R.id.checkChange); - image_dialog_text.setText(R.string.explain_text_delete); //삭제된 이미지 글 - TextView image_dialog_text_explain = image_dialog.findViewById(R.id.explainAlarm1); //업데이트 알림 - image_dialog_text_explain.setText(R.string.formatted_text_delete); - - return image_dialog; - } - return image_dialog; - } - - public void show_image_dialog_notificaiton(Context context,boolean is_add){ - if (context instanceof Activity) { - ((Activity) context).runOnUiThread(new Runnable() { - @Override - public void run() { - image_dialog = getImage_dialog(is_add); - image_dialog.show(); - - } - }); - } - } - - //분석할 이미지가 없을 시에 분석할 이미지가 없다는 다이얼로그가 띄워짐 - public void show_no_image_dialog_notification(Context context){ - if (context instanceof Activity) { - ((Activity) context).runOnUiThread(new Runnable() { - @Override - public void run() { - no_image_dialog.show(); - } - }); - } - } - -} diff --git a/app/src/main/java/com/example/metasearch/manager/ImageNotificationManager.java b/app/src/main/java/com/example/metasearch/manager/ImageNotificationManager.java deleted file mode 100644 index 921cc436..00000000 --- a/app/src/main/java/com/example/metasearch/manager/ImageNotificationManager.java +++ /dev/null @@ -1,301 +0,0 @@ -package com.example.metasearch.manager; - -import static android.content.ContentValues.TAG; -import static android.content.Context.NOTIFICATION_SERVICE; - -import static java.lang.Thread.sleep; - -import android.app.Activity; -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.os.Build; -import android.os.Handler; -import android.os.Looper; -import android.util.Log; - -import androidx.core.app.NotificationCompat; -import androidx.work.OneTimeWorkRequest; -import androidx.work.WorkManager; - -import com.example.metasearch.R; -import com.example.metasearch.ui.activity.MainActivity; -import com.example.metasearch.ui.fragment.HomeFragment; - -public class ImageNotificationManager { - private NotificationManager mNotificationManager; //알림 매니저 - private NotificationCompat.Builder notifyBuilder; //알림 빌더 - private static final String CHNANNEL_ID ="channelID";//channel을 구분하기 위한 ID - private static final String COMPLETE_CHNANNEL_ID = "completeChannelID"; //channel을 구분하기 위한 ID - private static final String COMPLETE_CHANNEL_ID = "completeChannelID"; //마지막 알림 channelID - private static final int NOTIFICATION_ID = 0; //Notification에 대한 ID 생성 - private NotificationChannel notificationChannel; -// private int progressMax = 100; - private int progressCurrent = 0; - private boolean is_finish_analyze = false; - private boolean isCanceled = false; //작업을 중단하기 위한 플래그 - private static ImageNotificationManager instance; - private Handler handler; //ui 관리 handler - private Runnable notificationRunnable; // 알림 진행 중 runnable - private boolean isCompleteNotification = false; - private ImageNotificationManager(Context context){ - mNotificationManager = (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE); - //업데이트 알람 생성 - makeUpdateNotificationChannel(context); - - //완료 업데이트 알람 생성 - makeCompleteNotificationChannel(context); - - } - - // 업데이트 알림 채널 생성 - public void makeUpdateNotificationChannel(Context context){ - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - //Channel 정의 생성자 - notificationChannel = new NotificationChannel(CHNANNEL_ID, "Update Image Analyze Notification", NotificationManager.IMPORTANCE_LOW); - //channel에 대한 기본 설정 - notificationChannel.setDescription("update image analyze notification"); - - //Manager을 이용하여 Channel 생성 - mNotificationManager.createNotificationChannel(notificationChannel); - - } - } - - // 완료 알림 채널 생성 - public void makeCompleteNotificationChannel(Context context){ - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - // int importance = NotificationManager.IMPORTANCE_HIGH; // 중요도 설정 - mNotificationManager.cancel(NOTIFICATION_ID); - mNotificationManager = (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE); - notificationChannel = new NotificationChannel(COMPLETE_CHNANNEL_ID,"Complete Image Analyze Notification",mNotificationManager.IMPORTANCE_HIGH); - notificationChannel.setDescription("complete image analyze notification"); - notificationChannel.enableVibration(true); - notificationChannel.enableLights(true); - notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC); - - // 채널에 대한 추가 설정 가능 (예: 진동 패턴 설정) - - // NotificationManager를 통해 채널 생성 - mNotificationManager.createNotificationChannel(notificationChannel); - -// notifyBuilder = new NotificationCompat.Builder(context, CHNANNEL_ID) -// .setContentTitle("이미지 분석 중...") //알림 제목 설정 -// .setContentText("이미지 분석이 시작됩니다.") //알림 내용 설정 -// .setSmallIcon(R.drawable.baseline_image_search_24) //알림 아이콘 설정 -// .setProgress(progressMax, progressCurrent, false); - } - } - - - public static ImageNotificationManager getImageNotification(Context context){ - if(instance == null){ - instance = new ImageNotificationManager(context); - } - return instance; - } - - //Builder를 설정하는 함수 - public NotificationCompat.Builder settingBuilder(Context context,String channelID){ - if(channelID.equals(CHNANNEL_ID)){ - //새로운 알림 생성 - notifyBuilder = new NotificationCompat.Builder(context, CHNANNEL_ID) - .setContentTitle("이미지 분석 중...") //알림 제목 설정 - .setContentText("이미지 분석 중 입니다.") //알림 내용 설정 - .setSmallIcon(R.drawable.baseline_image_search_24) //알림 아이콘 설정 - .setProgress(100, progressCurrent, false); - } - else if(channelID.equals(COMPLETE_CHANNEL_ID)){ - //새로운 알림 생성 - notifyBuilder = new NotificationCompat.Builder(context, COMPLETE_CHNANNEL_ID) - .setContentTitle("이미지 분석 완료") // 새로운 제목 설정 - .setContentText("검색을 시작해보세요.") // 새로운 내용 설정 - .setAutoCancel(true) - .setPriority(NotificationCompat.PRIORITY_HIGH) - .setSmallIcon(R.drawable.ic_launcher_foreground); // 새로운 아이콘 설정 - } - return notifyBuilder; - } -// public void show_notificaiton(Context context,int imageListSize){ -// if (context instanceof Activity) { -// ((Activity) context).runOnUiThread(new Runnable() { -// @Override -// public void run() { -// showUpdateNotificationProgress(context,imageListSize); -// -// } -// }); -// } -// } - - // 진행 중인 알림창 띄우기 -// public void showUpdateNotificationProgress(Context context, int imageListSize){ -// final int totalImages = imageListSize; -// final int updateInterval = 30000; // 30초 동안 이미지 처리 -// notifyBuilder = settingBuilder(context,CHNANNEL_ID); -// -// //handler를 사용하여 비동기적으로 알림 업데이트 -// handler = new Handler(Looper.getMainLooper()); -// -// notificationRunnable = new Runnable() { -// int currentProgress = 0; -// -// @Override -// public void run() { -// if (isCanceled) { -// //작업이 중간에 강제 취소된 경우 -// //100% 강제 완료 or 제대로 다 끝낸 후 종료 -// Log.d(TAG,"isCanceled는 true로 변경"); -// notifyBuilder.setProgress(progressMax, progressMax, false); -// notifyBuilder.setContentTitle("이미지 분석 100% 완료"); -// notifyBuilder.setContentText("잠시만 기다려주세요..."); -// mNotificationManager.notify(NOTIFICATION_ID, notifyBuilder.build()); -// -// if (!is_finish_analyze) //false 이면 -// is_finish_analyze = true;//프로그래스 바 마지막까지 완료 -// isCanceled = false; //다시 원상복구 -// return; -// } -// if (currentProgress <= 100) { -// //알림 업데이트 -// notifyBuilder.setProgress(progressMax, currentProgress, false); -// notifyBuilder.setContentTitle("이미지 분석 " + currentProgress + "% 완료"); -// Log.d(TAG, "현재 완료 숫자 :" + currentProgress); -// mNotificationManager.notify(NOTIFICATION_ID, notifyBuilder.build()); -// -// //다음 업데이트를 위한 프로그래스 증가 -// currentProgress += (100 / totalImages); -// -// //다음 업데이트 스케줄 //30초 이후 -// handler.postDelayed(this, updateInterval); -// -// //마지막 업데이트에서 100% 설정 -// if (currentProgress >= 100) { -// notifyBuilder.setProgress(progressMax, progressMax, false); -// notifyBuilder.setContentTitle("이미지 분석 100% 완료"); -// notifyBuilder.setContentText("잠시만 기다려주세요..."); -// -// is_finish_analyze = true; //프로그래스바 마지막까지 완료 -// } -// } -// } -// }; -// //첫 업데이트 시작 -// handler.post(notificationRunnable); -// } - - public void showUpdateNotificationProgress(Context context, int imageListSize) { - notifyBuilder = settingBuilder(context, CHNANNEL_ID); - - handler = new Handler(Looper.getMainLooper()); - notificationRunnable = new Runnable() { - ImageNotificationManager imageNotificationManager = ImageNotificationManager.getImageNotification(context); - private int progressCurrent; - - @Override - public void run() { - this.progressCurrent = imageNotificationManager.getProgressCurrent(); - Log.d(TAG, "현재 알람에서의 프로그래스 숫자 : " + progressCurrent); - if (progressCurrent < imageListSize && !isCanceled) { - // 알림 업데이트 - notifyBuilder.setProgress(imageListSize, progressCurrent, false); - notifyBuilder.setContentTitle("이미지 분석 "+progressCurrent+"/"+imageListSize+"개 완료"); - Log.d(TAG, "현재 완료 숫자 :" + progressCurrent); - mNotificationManager.notify(NOTIFICATION_ID, notifyBuilder.build()); - - // 5초 이후 다음 업데이트 스케줄 - handler.postDelayed(this, 5000); - } else { - // 작업이 완료되거나 취소되었을 때 알림 업데이트 종료 - if (progressCurrent >= imageListSize) { - notifyBuilder.setProgress(imageListSize, imageListSize, false); - notifyBuilder.setContentTitle("이미지 분석 완료"); - notifyBuilder.setContentText("잠시만 기다려주세요..."); - mNotificationManager.notify(NOTIFICATION_ID, notifyBuilder.build()); - - // 프로그래스바 마지막까지 완료 - is_finish_analyze = true; - } - - if (isCanceled) { - notifyBuilder.setProgress(imageListSize, imageListSize, false); - notifyBuilder.setContentTitle("이미지 분석 완료"); - notifyBuilder.setContentText("잠시만 기다려주세요..."); - mNotificationManager.notify(NOTIFICATION_ID, notifyBuilder.build()); - - if (!is_finish_analyze) - is_finish_analyze = true; - } - } - } - }; - // 첫 업데이트 시작 - handler.post(notificationRunnable); - } - - - public void setProgressCurrent (int count){ //현재 프로그래스 숫자 변경 - this.progressCurrent = count; - } - public int getProgressCurrent(){ - return this.progressCurrent; - } - - - - - - // 종료된 알림창 띄우기 - public void showCompleteNotification(Context context){ - // 전의 채널 알림 종료 - mNotificationManager.cancel(NOTIFICATION_ID); - - // 새로운 알림 띄우기 - notifyBuilder = settingBuilder(context,COMPLETE_CHANNEL_ID); - -// //알림을 클릭하면 MainActivity로 이동하게 된다. -// // MainActivity로 이동하는 Intent 생성 -// Intent intent = new Intent(context, MainActivity.class); -// // 사용자가 알림을 클릭했을 때 기존에 있던 액티비티 스택 위에 새 액티비티를 띄우도록 설정 -// intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); -// // PendingIntent 생성 -// // 안드로이드 12 (API 수준 31) 이상에서는 FLAG_IMMUTABLE 또는 FLAG_MUTABLE을 명시적으로 지정해야 합니다. -// int flags = PendingIntent.FLAG_UPDATE_CURRENT; -// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { -// flags |= PendingIntent.FLAG_IMMUTABLE; // FLAG_MUTABLE을 사용할 필요가 있는 경우에는 이를 FLAG_IMMUTABLE 대신 사용하세요. -// } -// PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, flags); -// -// // 알림 빌더에 PendingIntent 설정 -// notifyBuilder.setContentIntent(pendingIntent); - - // 알림 발송 - mNotificationManager.notify(NOTIFICATION_ID, notifyBuilder.build()); - isCompleteNotification = true; -// OneTimeWorkRequest notificationWork = new OneTimeWorkRequest.Builder(NotificationWorker.class) -// .build(); -// WorkManager.getInstance(context).enqueue(notificationWork); - - } - - //현재 화면에 완료 알람이 띄워져있는지 확인하는 함수 - public boolean getIsCompleteNotification(){ - return this.isCompleteNotification; - } - - //진행 중이던 프로그래스 바 강제 100%로 종료시키는 함수 - public void cancelProceedProgressbar() throws InterruptedException { - //플래그를 설정하여 runnable이 다음 살행을 중지하도록 함 - isCanceled = true; - - sleep(2000); //2초 후에 해당 runnable 삭제 - handler.removeCallbacks(notificationRunnable); //현재 예약된 runnable을 제거함 - setProgressCurrent(0); //현재 진행률 0%로 설정 - Log.d(TAG,"cancelProceedProgressbar 함수 실행"); - } - -} diff --git a/app/src/main/java/com/example/metasearch/manager/ImageServiceRequestManager.java b/app/src/main/java/com/example/metasearch/manager/ImageServiceRequestManager.java deleted file mode 100644 index f500006b..00000000 --- a/app/src/main/java/com/example/metasearch/manager/ImageServiceRequestManager.java +++ /dev/null @@ -1,323 +0,0 @@ -package com.example.metasearch.manager; - -import static java.lang.Thread.sleep; - -import android.app.Dialog; -import android.app.Notification; -import android.app.NotificationManager; -import android.content.Context; -import android.util.Log; - -import androidx.core.app.NotificationCompat; -import androidx.work.OneTimeWorkRequest; -import androidx.work.WorkManager; - -import com.example.metasearch.data.dao.AnalyzedImageListDatabaseHelper; -import com.example.metasearch.data.dao.DatabaseHelper; -import com.example.metasearch.utils.DatabaseUtils; -import com.example.metasearch.network.interfaces.ImageAnalysisCompleteListener; -import com.example.metasearch.network.api.ApiService; -import com.example.metasearch.utils.GalleryImageManager; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Map; -import java.util.concurrent.ExecutionException; - -public class ImageServiceRequestManager { - private ImageAnalysisCompleteListener listener; - private static final String TAG = "ImageUploader"; //로그에 표시될 태그 - private Context context; //Context 객체 - private DatabaseHelper databaseHelper; //sqlite 데이터베이스 접근 객체 - private AnalyzedImageListDatabaseHelper analyzedImageListDatabaseHelper; - private ImageAnalyzeListManager imageAnalyzeListController; //이미지 분석된 리스트 객체 - private ApiService aiService; - private AIRequestManager aiRequestManager; - private WebRequestManager webRequestManager; - private ApiService webService; - private ImageDialogManager imageDialogManager; // 다이얼로그 매니저 - private ImageNotificationManager imageNotificationManager; // 알림 매니저 - private static ImageServiceRequestManager instance; - private Dialog image_dialog; //이미지 분석/삭제 관련 다이얼로그 - private NotificationManager mNotificationManager; // 알림 매니저 - private NotificationCompat.Builder notifyBuilder; // 알림 빌더 - private Notification notificationChannel; //알림 채널 - private int progressMax = 100; - private int progressCurrent = 0; - private boolean is_finish_analyze = false; //현재 모든 분석이 100% 되었는지 여부 - private static final String CHANNEL_ID = "channelID"; //channel을 구분하기 위한 ID - private static final String COMPLETE_CHANNEL_ID = "completeChannelID"; //channel을 구분하기 위한 ID(완료) - private static final int NOTIFICATION_ID = 0; //Notificaiton에 대한 ID 생성 - private String DBName; //클라이언트가 사용하는 neo4j dbName - private boolean isAnalyzing = false; - - - private ImageServiceRequestManager(Context context, DatabaseHelper databaseHelper) { - this.context = context; - // 인물 데이터베이스 설정 - this.databaseHelper = databaseHelper; - // 분석된 이미지 데이터베이스 설정 -// this.imageAnalyzeListController = imageAnalyzeList; - this.imageAnalyzeListController = ImageAnalyzeListManager.getInstance(context); - this.imageDialogManager = ImageDialogManager.getImageDialogManager(context); //이미지 다이얼로그 객체 생성 - this.imageNotificationManager = ImageNotificationManager.getImageNotification(context); //이미지 알림 객체 생성 - - - } //생성자 - - public static ImageServiceRequestManager getInstance(Context context, DatabaseHelper databaseHelper){ - if (instance == null){ - instance = new ImageServiceRequestManager(context,databaseHelper); - } - return instance; - } - public void setImageAnalysisCompleteListener(ImageAnalysisCompleteListener listener) { - this.listener = listener; - } -// public boolean getIsAnalyzing(){ -// return this.isAnalyzing; -// } - public void getImagePathsAndUpload() throws IOException, ExecutionException, InterruptedException { // 갤러리 이미지 경로 / 데이터베이스의 모든 얼굴 byte 가져옴 -// this.isAnalyzing = true; - Log.d(TAG,"getImagePathsAndUpload 함수 들어옴"); - //aiRetrofit = AIHttpService.getInstance(AIserver_BASE_URL).getRetrofit(); -// webRetrofit = WebHttpService.getInstance(Webserver_BASE_URL).getRetrofit(); -// //aiService = aiRetrofit.create(ImageUploadService.class); -// webService = webRetrofit.create(ImageUploadService.class); - - aiRequestManager = AIRequestManager.getAiImageUploader(context); - aiService = aiRequestManager.getAiService(); - webRequestManager = WebRequestManager.getWebImageUploader(); - webService = webRequestManager.getWebService(); - ArrayList imagePaths = GalleryImageManager.getAllGalleryImagesUriToString(context); - Map dbImages = databaseHelper.getAllImages(); //데이터베이스에서 이미지를 byte로 로드 - - Log.d(TAG,"imagePaths의 사이즈 : "+imagePaths.size()); - Log.d(TAG,"데이터베이스 이미지 수: " + (dbImages != null ? dbImages.size() : "null")); - - - if(!imagePaths.isEmpty()){ - Log.d(TAG,"imagesPath는 안 비어있음"); - } - //갤러리 이미지 경로 리스트 또는 데이터베이스 바이트 리스트가 null이어도 일단은 전달 - DBName = DatabaseUtils.getPersistentDeviceDatabaseName(context); - uploadImage(imagePaths, dbImages, DBName); //이미지를 각각의 파일로 업로드 하는 함수 호출 - } -// public void show_image_dialog_notificaiton(Context context,boolean is_add){ -// if (context instanceof Activity) { -// ((Activity) context).runOnUiThread(new Runnable() { -// @Override -// public void run() { -// image_dialog = imageDialogManager.getImage_dialog(is_add); -// image_dialog.show(); -// -// } -// }); -// } -// } - //이미지를 업로드하는 함수 - public void uploadImage(ArrayList imagesPaths, Map dbBytes,String DBName) throws IOException, ExecutionException, InterruptedException { - Log.d(TAG,"UplaodImage 함수에 들어옴"); - - //일단 추가하거나 삭제할 이미지가 있는지 체크한다. - ArrayList deletePaths = checkDeleteImagePath(imagesPaths); - ArrayList addImagePaths = checkAddImagePath(imagesPaths); - - Log.d(TAG,"uploadImage deletePaths 사이즈 : "+deletePaths.size()); - Log.d(TAG,"uploadImage addPaths 사이즈 : "+addImagePaths.size()); - - if (!addImagePaths.isEmpty()){ //추가 분석할 것이 있다면 - Log.d(TAG,"!addImagePaths.isEmpty()임"); - int size = 0; - // 다이얼로그 띄움 - imageDialogManager.show_image_dialog_notificaiton(context,true); - if (!deletePaths.isEmpty()){ // 만약 삭제 이미지가 있다면 - // 삭제 이미지 사이즈를 size에 합침 - size+=deletePaths.size(); - } - // 알림 띄움 - //imageNotificationManager.showUpdateNotificationProgress(context, size+=addImagePaths.size()); //분석할 이미지의 개수를 size에 합침 - //imageNotificationManager.show_notificaiton(context,size+=addImagePaths.size()); - - } - else if(!deletePaths.isEmpty()){ //삭제할 것이 있다면(추가 분석할 것은 없음) - Log.d(TAG,"!deletePaths.isEmpty()임"); - - // 다이얼로그 띄움 - imageDialogManager.show_image_dialog_notificaiton(context,false); - // 삭제만 있으면 알림 안 띄움 (이미지 분석을 기준으로 알림을 띄우는 것) - } - else if(addImagePaths.isEmpty() && deletePaths.isEmpty()){ // 추가/삭제 할 것이 없음 - Log.d(TAG,"추가 삭제할 것이 없음"); - imageDialogManager.show_no_image_dialog_notification(context); - // 분석이 없으면 알림할 필요없음 - } - - //만약 삭제할 이미지나 추가 이미지가 있으면 - if (!deletePaths.isEmpty() || !addImagePaths.isEmpty()) { - Log.d(TAG,"삭제할 이미지나 추가할 이미지가 있음"); - //ArrayList safeDeletePaths = deletePaths != null ? deletePaths : new ArrayList<>(); - //ArrayList safeAddImagePaths = addImagePaths != null ? addImagePaths : new ArrayList<>(); - if(dbBytes!=null){ - request_image_AIServer(addImagePaths,deletePaths,DBName); - //이미지 분석이 시작된다는 첫 번째 요청 실행 ( db의 faces 삭제) -// aiRequestManager.firstUploadImage(DBName).thenRun(()->{ -// //db의 faces에 얼굴 업로드 -// aiRequestManager.uploadDBImage(dbBytes, DBName).thenRun(() -> { //콜백 설정함 //db 요청 끝나고 사진 분석 요청 보냄 -// //추가나 삭제 이미지를 서버에 전송 -//// request_image_AIServer(addImagePaths,deletePaths,DBName); -// }); -// }); - } - else{ //데이터베이스에 이미지가 없는 경우, - request_image_AIServer(addImagePaths,deletePaths,DBName); - } - } - } - //이미지 분석 리스트를 통해 해당 이미지가 추가될 이미지인지, 삭제될 이미지인지 확인하고 그에 맞게 서버에 요청하는 함수 - public ArrayList checkAddImagePath(ArrayList imagesPaths){ -// //추가할 것이 있는지 찾을 것 - ArrayList addImagePaths = new ArrayList<>(); - addImagePaths = imageAnalyzeListController.checkAddImagePath(imagesPaths); - return addImagePaths; - - } - public ArrayList checkDeleteImagePath(ArrayList imagesPaths){ - Log.d(TAG,"checkDeleteImagePath 함수에 들어옴"); - ArrayList deleteImagePaths = new ArrayList<>(); - deleteImagePaths = imageAnalyzeListController.checkDeleteImagePath(imagesPaths); - return deleteImagePaths; - } - - - public void request_image_AIServer(ArrayListaddImagePaths,ArrayListdeleteImagePaths,String DBName) throws IOException { - Log.d(TAG,"request_image_AIServer 함수에 들어옴"); - Log.d(TAG,"추가 분석할 리스트 사이즈 : "+addImagePaths.size()); - Log.d(TAG,"삭제할 리스트 사이즈 : "+deleteImagePaths.size()); - boolean isAddExit = !addImagePaths.isEmpty();; //추가 요청이 있는지 확인 - boolean isDeleteExit = !deleteImagePaths.isEmpty(); //삭제 요청이 있는지 확인 - - - if(isDeleteExit && isAddExit){ // 삭제 요청 o, 추가 요청 o - Log.d(TAG,"삭제 요청과 추가 요청이 있었음"); - - //web 서버에 삭제 관련 이미지 이름 전송 - webRequestManager.uploadDeleteGalleryImage(deleteImagePaths,DBName); - - //AI 서버에 삭제 관련 이미지 이름 전송 - aiRequestManager.uploadDeleteGalleryImage(databaseHelper,deleteImagePaths,DBName).thenRun(() -> { //콜백 설정함 - Log.d(TAG,"삭제 끝 추가 분석 시작하려고 함"); - Log.d(TAG,"추가 분석 이미지 개수 : "+addImagePaths.size()); - //웹 서버에 추가 분석 이미지 전송 - webRequestManager.uploadAddGalleryImage(addImagePaths, DBName); - //AI 서버에 추가 분석 이미지 전송 - try { - aiRequestManager.uploadAddGalleryImage(addImagePaths,DBName).thenRun(() -> { - //콜백 설정 - - //추가 이미지 경로 리스트, 삭제 이미지 경로 리스트 초기화 - imageAnalyzeListController.clearAddDeleteImageList(); - //AI 서버에 모든 요청이 보내졌다는 마무리 요청 - aiRequestManager.completeUploadImage(databaseHelper,DBName).thenRun(()->{ - //이미지 이름과 input 이름이 다른 것을 확인하여 neo4j서버에 csv 이름 변경을 요청함 - requestChangeName(); - informCompleteImageAnalyze(); - completeAnalysis(); - // this.isAnalyzing = false; - - }); - }); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); - } - else if(!isAddExit && isDeleteExit){ //삭제 요청 o, 추가 요청 x - //웹 서버에 삭제 관련 이미지 전송 - //webRequestManager.uploadDeleteGalleryImage(webService,deleteImagePaths,DBName); -//웹 서버에 삭제 관련 이미지 전송 - webRequestManager.uploadDeleteGalleryImage(deleteImagePaths,DBName); - //AI 서버에 삭제 관련 이미지 이름 전송 - aiRequestManager.uploadDeleteGalleryImage(databaseHelper,deleteImagePaths,DBName).thenRun(() -> { //콜백 설정함 -// //추가 이미지 경로 리스트, 삭제 이미지 경로 리스트 초기화 -// imageAnalyzeListController.clearAddDeleteImageList(); - //추가 이미지 경로 리스트, 삭제 이미지 경로 리스트 초기화 - imageAnalyzeListController.clearAddDeleteImageList(); - - //AI 서버에 모든 요청이 마무리 되었다는 요청 - aiRequestManager.completeUploadImage(databaseHelper,DBName).thenRun(()->{ - Log.d(TAG,"모든 이미지 전송 완료"); - requestChangeName(); - informCompleteImageAnalyze(); - completeAnalysis(); -// this.isAnalyzing = false; - - }); - }); - } - else if(isAddExit) { //삭제 요청 x, 추가 요청 o - Log.d(TAG, "추가 작업만 진행"); - - //웹 서버에 추가 분석 이미지 전송 - webRequestManager.uploadAddGalleryImage(addImagePaths, DBName); - - //AI 서버에 추가 분석 이미지 전송 - aiRequestManager.uploadAddGalleryImage(addImagePaths,DBName).thenRun(() -> { - //콜백 설정 -// //추가 이미지 경로 리스트, 삭제 이미지 경로 리스트 초기화 -// imageAnalyzeListController.clearAddDeleteImageList(); - //추가 이미지 경로 리스트, 삭제 이미지 경로 리스트 초기화 - imageAnalyzeListController.clearAddDeleteImageList(); - //AI 서버에 모든 요청이 보내졌다는 마무리 요청 - aiRequestManager.completeUploadImage(databaseHelper,DBName).thenRun(()->{ - //이미지 이름과 input 이름이 다른 것을 확인하여 neo4j서버에 csv 이름 변경을 요청함 - requestChangeName(); - informCompleteImageAnalyze(); - completeAnalysis(); -// this.isAnalyzing = false; - - }); - }); - } - - - } - // 이미지 분석이 완료된 후 호출 - public void completeAnalysis() { - if (listener != null) { - listener.onImageAnalysisComplete(); - } - } - // 이미지 이름과 input 이름이 다른 것들이 들어있는 해쉬맵 함수를 순회하며 neo4j서버에서 csv를 변경하도록 요청함 - public void requestChangeName() { - Map misMatchMap = databaseHelper.getMismatchedImageInputNames(); - if (!misMatchMap.isEmpty()){ - for (Map.Entry misMatchMapEntity : misMatchMap.entrySet()){ - //key = oldName, value = newName - //neo4j 서버에게 oldName과 newName을 보낸다. - webRequestManager.changePersonName(DBName,misMatchMapEntity.getKey(),misMatchMapEntity.getValue()); - } - } - } - - //이미지 분석이 끝났다는 것을 알리는 함수 - public void informCompleteImageAnalyze(){ -// try { -// // 해당 프로그래스 바 강제 100% 종료, runnable에서 삭제 -// imageNotificationManager.cancelProceedProgressbar(); -// } catch (InterruptedException e) { -// throw new RuntimeException(e); -// } - // imageNotificationManager.showCompleteNotification(context); - // 완료 알림을 화면에 띄움 - - //해당 work가 imageNotificationManager.showCompleteNotification(context)를 호출하도록 되어있음 - OneTimeWorkRequest notificationWork = new OneTimeWorkRequest.Builder(NotificationWorker.class) - .build(); - WorkManager.getInstance(context).enqueue(notificationWork); - - - } - - -} diff --git a/app/src/main/java/com/example/metasearch/manager/NotificationWorker.java b/app/src/main/java/com/example/metasearch/manager/NotificationWorker.java deleted file mode 100644 index 241449cf..00000000 --- a/app/src/main/java/com/example/metasearch/manager/NotificationWorker.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.example.metasearch.manager; - -import android.content.Context; - -import androidx.annotation.NonNull; -import androidx.work.Worker; -import androidx.work.WorkerParameters; -import androidx.work.WorkManager; -import androidx.work.OneTimeWorkRequest; - - -public class NotificationWorker extends Worker { - private ImageNotificationManager imageNotificationManager; - - public NotificationWorker( - @NonNull Context context, - @NonNull WorkerParameters params) { - super(context, params); - this.imageNotificationManager = ImageNotificationManager.getImageNotification(context); - } - - @NonNull - @Override - public Result doWork() { - // 여기에 알림 전송 로직을 넣으세요. - // 예를 들어, 위에 정의된 showCompleteNotification 메서드를 호출합니다. - - imageNotificationManager.showCompleteNotification(getApplicationContext()); - // 작업이 성공적으로 완료되었음을 나타냅니다. - return Result.success(); - } -} - diff --git a/app/src/main/java/com/example/metasearch/manager/WebRequestManager.java b/app/src/main/java/com/example/metasearch/manager/WebRequestManager.java deleted file mode 100644 index a6629134..00000000 --- a/app/src/main/java/com/example/metasearch/manager/WebRequestManager.java +++ /dev/null @@ -1,377 +0,0 @@ -package com.example.metasearch.manager; - -import static androidx.constraintlayout.helper.widget.MotionEffect.TAG; - -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.example.metasearch.utils.HttpHelper; -import com.example.metasearch.network.interfaces.WebServerDeleteEntityCallbacks; -import com.example.metasearch.network.interfaces.WebServerPersonDataUploadCallbacks; -import com.example.metasearch.network.interfaces.WebServerPersonFrequencyUploadCallbacks; -import com.example.metasearch.network.interfaces.WebServerQueryCallbacks; -import com.example.metasearch.network.interfaces.WebServerUploadCallbacks; -import com.example.metasearch.data.model.Person; -import com.example.metasearch.network.request.ChangeNameRequest; -import com.example.metasearch.network.request.DeleteEntityRequest; -import com.example.metasearch.network.request.NLQueryRequest; -import com.example.metasearch.network.request.PersonFrequencyRequest; -import com.example.metasearch.network.response.ChangeNameResponse; -import com.example.metasearch.network.response.DeleteEntityResponse; -import com.example.metasearch.network.response.PersonFrequencyResponse; -import com.example.metasearch.network.response.PhotoNameResponse; -import com.example.metasearch.network.response.PhotoResponse; -import com.example.metasearch.network.response.TripleResponse; -import com.example.metasearch.network.api.ApiService; -import com.google.gson.Gson; - -import java.io.File; -import java.io.IOException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.util.ArrayList; -import java.util.stream.Collectors; - -import okhttp3.MediaType; -import okhttp3.MultipartBody; -import okhttp3.RequestBody; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -public class WebRequestManager { - private static final String Webserver_BASE_URL = "http://113.198.85.6"; // web 서버의 기본 url - private static WebRequestManager webImageUploader; - private ApiService webService; - - private WebRequestManager(){ - //this.aiService = AIHttpService.getInstance(AIserver_BASE_URL); - this.webService = HttpHelper.getInstance(Webserver_BASE_URL).getRetrofit().create(ApiService.class); - } - - public ApiService getWebService(){ - return webService; - } - - public static WebRequestManager getWebImageUploader(){ - Log.d(TAG,"AIImageUploaderController 함수 들어옴 객체 생성 or 반환"); - if(webImageUploader == null){ - webImageUploader = new WebRequestManager(); - - } - return webImageUploader; - } - - //추가된 이미지 관해 서버에 전송 -// public void uploadAddGalleryImage(DatabaseHelper databaseHelper, ImageUploadService service, File imageFile, String source){ -// Log.d(TAG,"web의 uploadAddGalleryImage 안에 들어옴"); -// //List> futuresList = new ArrayList<>(); -// Log.d(TAG,"addImge 이름 : " + imageFile.getName()); -// RequestBody requestBody; -// MultipartBody.Part imagePart; -// //추가 이미지 분석이 필요한 이미지를 전달 -// requestBody = RequestBody.create(MediaType.parse("image"),imageFile); -// //파일의 경로를 해당 이미지의 이름으로 설정함 -// imagePart = MultipartBody.Part.createFormData("addImage",imageFile.getName(),requestBody); -// -// //이미지 출처 정보를 전송할 RequestBody 생성 -// RequestBody sourceBody = RequestBody.create(MediaType.parse("text/plain"),source); //DB이름과 어디에 저장되어야하는지에 관한 정보를 전달 -// -// //API 호출 -// Call call = service.uploadWebAddImage(imagePart,sourceBody); //이미지 업로드 API 호출 -// call.enqueue(new Callback() { //비동기 -// @Override -// public void onResponse(Call call, Response response) { -// if (response.isSuccessful()) { -// Log.e(TAG, "Image upload 성공: " + response.message()); -// } -// } -// @Override -// public void onFailure(Call call, Throwable t) { -// Log.e(TAG, "추가 이미지 업로드 실패함" + t.getMessage()); -// } -// }); -// } - - public void uploadAddGalleryImage(ArrayListaddImagePaths, String dbName){ - Log.d(TAG,"web의 uploadAddGalleryImage 안에 들어옴"); - - for(String addImagePath : addImagePaths){ - //Log.d(TAG,"addImge 이름 : " + imageFile.getName()); - File imageFile = new File(addImagePath); - - // 파일 이름을 URL 인코딩 - String fileName = null; - try { - fileName = URLEncoder.encode(imageFile.getName(), "UTF-8"); - } catch (UnsupportedEncodingException e) { - Log.e(TAG, "파일 이름 인코딩 실패: " + e.getMessage()); - continue; - } - - - RequestBody requestBody = RequestBody.create(MediaType.parse("image/*"), imageFile); - MultipartBody.Part imagePart = MultipartBody.Part.createFormData("image", fileName, requestBody); - - Call call = webService.uploadWebAddImage(imagePart, dbName); // sourceBody 대신 dbName을 직접 전달합니다. - call.enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { - Log.e(TAG, "Image upload 성공: " + response.message()); - } - } - - @Override - public void onFailure(Call call, Throwable t) { - Log.e(TAG, "추가 이미지 업로드 실패함" + t.getMessage()); - } - }); - } - - } - - - - - - // Web Server에 인물 빈도 수 요청 - public void getPersonFrequency(String dbName, List people, WebServerPersonFrequencyUploadCallbacks callbacks) { - List personNames = people.stream().map(Person::getInputName).collect(Collectors.toList()); - PersonFrequencyRequest request = new PersonFrequencyRequest(dbName, personNames); - Call call = webService.getPersonFrequency(request); - call.enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { - callbacks.onPersonFrequencyUploadSuccess(response.body()); - } else { - callbacks.onPersonFrequencyUploadFailure("Response from server was not successful."); - } - } - - @Override - public void onFailure(Call call, Throwable t) { - callbacks.onPersonFrequencyUploadFailure("Failed to retrieve data from server: " + t.getMessage()); - } - }); - } - - // Web Server로 인물 이름 or 사진 이름 전송 - public void sendPersonData(String personData, String dbName, WebServerPersonDataUploadCallbacks callbacks) { - // Gson 인스턴스 생성 - Gson gson = new Gson(); - - // dbName과 인물 정보(인물 이름 또는 사진 이름)을 포함하는 Map 객체 생성 - Map jsonMap = new HashMap<>(); - jsonMap.put("dbName", dbName); - jsonMap.put("personName", personData); - - // Map 객체를 JSON 문자열로 변환 - String jsonObject = gson.toJson(jsonMap); - - // JSON 문자열을 바디로 사용하여 RequestBody 객체 생성 - RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"), jsonObject); - - // POST 요청 보내기 - Call> sendCall = webService.sendPersonData(requestBody); - sendCall.enqueue(new Callback>() { - @Override - public void onResponse(Call> call, Response> response) { - if (response.isSuccessful()) { - Log.d("Upload", "Person data sent successfully"); - assert response.body() != null; - Log.d("Upload", "Common Photos: " + response.body()); - callbacks.onPersonDataUploadSuccess(response.body()); - } else { - callbacks.onPersonDataUploadFailure("Server responded with error"); - } - } - @Override - public void onFailure(Call> call, Throwable t) { - Log.e("Upload", "Error sending person data", t); - callbacks.onPersonDataUploadFailure(t.getMessage()); - } - }); - } - // Web Server로 Circle to Search 이미지 분석 결과 전송 - public void sendDetectedObjectsToWebServer(List detectedObjects, String dbName, WebServerUploadCallbacks callbacks) { - // Gson 인스턴스 생성 - Gson gson = new Gson(); - - // detectedObjects와 dbName을 포함하는 Map 객체 생성 - Map jsonMap = new HashMap<>(); - jsonMap.put("dbName", dbName); - jsonMap.put("properties", detectedObjects); - - // Map 객체를 JSON 문자열로 변환 - String jsonObject = gson.toJson(jsonMap); - - // JSON 문자열을 바디로 사용하여 RequestBody 객체 생성 - RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"), jsonObject); - - Log.d("e",jsonObject); - - // POST 요청 보내기 - Call sendCall = webService.sendDetectedObjects(requestBody); - sendCall.enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { - Log.d("Upload", "Detected objects sent successfully"); - assert response.body() != null; - Log.d("Upload", "Common Photos: " + response.body().getPhotos().getCommonPhotos()); - Log.d("Upload", "Individual Photos: " + response.body().getPhotos().getIndividualPhotos()); - callbacks.onWebServerUploadSuccess(response.body()); - } else { - callbacks.onWebServerUploadFailure("Server responded with error"); - } - } - @Override - public void onFailure(Call call, Throwable t) { - Log.e("Upload", "Error sending detected objects", t); - } - }); - } - - public void uploadDeleteGalleryImage(ArrayList deleteImagePaths, String dbName ){ - RequestBody requestBody; - MultipartBody.Part imagePart; - for(String deleteImagePath : deleteImagePaths){ - // 파일 이름을 URL 인코딩 - String deleteImageName = null; - try { - deleteImageName = URLEncoder.encode(deleteImagePath, "UTF-8"); - } catch (UnsupportedEncodingException e) { - Log.e(TAG, "파일 이름 인코딩 실패: " + e.getMessage()); - continue; - } - deleteImageName = deleteImageName.substring(deleteImageName.lastIndexOf(File.separator) + 1); - requestBody = RequestBody.create(MediaType.parse("filename"),deleteImageName); - //파일의 경로를 해당 이미지의 이름으로 설정함 - imagePart = MultipartBody.Part.createFormData("deleteImage",deleteImageName,requestBody); - RequestBody sourceBody = RequestBody.create(MediaType.parse("text/plain"),dbName); //DB이름과 어디에 저장되어야하는지에 관한 정보를 전달 - - - - //API 호출 - Call call = webService.uploadWebDeleteImage(imagePart,sourceBody); //이미지 업로드 API 호출 - call.enqueue(new Callback() { //비동기 - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { - Log.e(TAG, "delete Image upload 성공: " + response.message()); - - } - } - @Override - public void onFailure(Call call, Throwable t) { - Log.e(TAG, "삭제 이미지 업로드 실패함" + t.getMessage()); - } - }); - } - } - - public void changePersonName(String dbName, String oldName, String newName) { - ChangeNameRequest request = new ChangeNameRequest(dbName, oldName, newName); - webService.changeName(request).enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { - // 성공적으로 이름 변경 - Log.d("Name Change", "Success: " + response.body().getMessage()); - } else { - // 서버에서 정상적으로 처리하지 못했을 때 - try { - Log.e("Name Change", "Failed: " + response.errorBody().string()); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - @Override - public void onFailure(Call call, Throwable t) { - // 네트워크 문제 등으로 요청 자체가 실패 - Log.e("Name Change", "Error: " + t.getMessage()); - } - }); - } - // - public void sendQueryToWebServer(String dbName, String query, WebServerQueryCallbacks callbacks) { - Gson gson = new Gson(); - String jsonObject = gson.toJson(new NLQueryRequest(dbName, query)); - RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"), jsonObject); - - Call call = webService.sendCypherQuery(requestBody); - call.enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - if (response.isSuccessful()) { - PhotoNameResponse photoNameResponse = response.body(); - if (photoNameResponse != null && photoNameResponse.getPhotoName() != null) { - Log.d("PhotoNames", photoNameResponse.getPhotoName().toString()); - callbacks.onWebServerQuerySuccess(photoNameResponse); - } else { - Log.e("Response Error", "Received null response body or empty photos list"); - } - } else { - Log.e("Response Error", "Failed to receive successful response: " + response.message()); - } - } - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - callbacks.onWebServerQueryFailure(); - Log.e("Request Error", "Failed to send request to server", t); - } - }); - } - // 이미지 설명을 위한 속성을 받아옴 - public void fetchTripleData(String dbName, String photoName, Callback callback) { - Call call = webService.fetchTripleData(dbName, photoName); - call.enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful() && response.body() != null) { - callback.onResponse(call, response); - } else { - // 서버로부터 성공적인 응답을 받았지만, 응답 내용에 문제가 있을 때 - Log.e(TAG, "Error fetching triple data: " + response.message()); - callback.onFailure(call, new IOException("Response unsuccessful: " + response.message())); - } - } - @Override - public void onFailure(Call call, Throwable t) { - // 네트워크 문제 등 요청 자체에 실패한 경우 - Log.e(TAG, "Failure fetching triple data", t); - callback.onFailure(call, t); - } - }); - } - public void deleteEntity(String dbName, String entityName, WebServerDeleteEntityCallbacks callbacks) { - DeleteEntityRequest request = new DeleteEntityRequest(dbName, entityName); - Call call = webService.deleteEntity(request); - call.enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { - DeleteEntityResponse deleteEntityResponse = response.body(); - if (deleteEntityResponse != null) { - callbacks.onDeleteEntitySuccess(deleteEntityResponse.getMessage()); - } else { - callbacks.onDeleteEntityFailure("Response body is null"); - } - } else { - callbacks.onDeleteEntityFailure("Response unsuccessful: " + response.message()); - } - } - @Override - public void onFailure(Call call, Throwable t) { - callbacks.onDeleteEntityFailure("Request failed: " + t.getMessage()); - } - }); - } -} diff --git a/app/src/main/java/com/example/metasearch/network/api/ApiService.java b/app/src/main/java/com/example/metasearch/network/api/ApiService.java deleted file mode 100644 index 1a4f99d6..00000000 --- a/app/src/main/java/com/example/metasearch/network/api/ApiService.java +++ /dev/null @@ -1,115 +0,0 @@ -package com.example.metasearch.network.api; - -import com.example.metasearch.network.request.ChangeNameRequest; -import com.example.metasearch.network.request.DeleteEntityRequest; -import com.example.metasearch.network.request.PersonFrequencyRequest; -import com.example.metasearch.network.response.ChangeNameResponse; -import com.example.metasearch.network.response.CircleDetectionResponse; -import com.example.metasearch.network.response.DeleteEntityResponse; -import com.example.metasearch.network.response.PersonFrequencyResponse; -import com.example.metasearch.network.response.PhotoResponse; -import com.example.metasearch.network.response.PhotoNameResponse; -import com.example.metasearch.network.response.TripleResponse; -import com.example.metasearch.network.response.UploadResponse; -import com.example.metasearch.network.request.OpenAIRequest; -import com.example.metasearch.network.response.OpenAIResponse; - -import java.util.List; - -import retrofit2.Call; -import retrofit2.http.Body; -import retrofit2.http.GET; -import retrofit2.http.Header; -import retrofit2.http.Headers; -import retrofit2.http.POST; -import okhttp3.MultipartBody; -import okhttp3.RequestBody; -import retrofit2.http.Multipart; -import retrofit2.http.Part; -import retrofit2.http.Path; -import retrofit2.http.Query; - -public interface ApiService { - // OpenAI service - @Headers("Content-Type: application/json") - @POST("/v1/chat/completions") - Call createChatCompletion( - @Header("Authorization") String authToken, @Body OpenAIRequest body); - // AI 서버에 이미지 분석 요청(circle to search) - @Multipart - @POST("android/circle_search") - Call uploadImageAndCircles( - @Part MultipartBody.Part image, - @Part("dbName")RequestBody dbName, - @Part("circles") RequestBody circles - ); - // Web 서버에 이미지 속성 전송 후 이미지 전송 요청(circle to search) - @POST("android/circleToSearch") - Call sendDetectedObjects(@Body RequestBody detectedObjects); - // Web 서버에 사이퍼 쿼리 전송 후 이미지 전송 요청(NL search) - @POST("/nlqsearch") - Call sendCypherQuery(@Body RequestBody body); - // Web 서버에 인물 이름 or 사진 이름 전송 후 이미지 전송 요청(people search) - @POST("personsearch") - Call> sendPersonData(@Body RequestBody body); - // Web 서버에 인물 데이터(인물 이름) 수정 요청 - @POST("changename") - Call changeName(@Body ChangeNameRequest request); - // Web 서버에 인물 빈도수 요청 - @POST("/api/peoplefrequency") - Call getPersonFrequency(@Body PersonFrequencyRequest request); - // Web 서버에 사진 속성(이미지 설명 출력에 필요) 요청 - @GET("/api/photoTripleData/{dbName}/{photoName}") - Call fetchTripleData(@Path("dbName") String dbName, @Path("photoName") String photoName); - // Web 서버에 노드 삭제 요청 - @POST("neo4j/deleteEntity/") - Call deleteEntity(@Body DeleteEntityRequest request); - // 이미지 분석 서버에 인물 삭제 요청 - @Multipart - @POST("android/delete_person") -// Call deletePerson(@Part DeleteEntityRequest request); - Call deletePerson(@Part("dbName")RequestBody dbName, @Part("deletePerson")RequestBody deletePerson); - - - //AI서버에 추가 이미지 분석 요청 - @Multipart - @POST("android/upload_add") - Call uploadAddImage(@Part MultipartBody.Part image, @Part("dbName")RequestBody dbName); - - //AI서버에 삭제 이미지 요청 - //AI서버에 삭제 이미지 요청 - @Multipart - @POST("android/upload_delete") - Call UploadDeleteImage(@Part MultipartBody.Part filename, @Part("dbName")RequestBody dbName); - - //AI서버에 데이터베이스 이미지 전송 요청 - @Multipart - @POST("android/upload_database") - Call uploadDatabaseImage(@Part MultipartBody.Part filename, @Part("dbName")RequestBody dbName); - - @Multipart - @POST("android/upload_first") - Call upload_first(@Part("first")RequestBody first,@Part("dbName")RequestBody dbName); - - //AI서버에 마지막 전송 알림 요청 - @Multipart - @POST("android/upload_finish") - Call upload_finish(@Part("finish")RequestBody finish,@Part("dbName")RequestBody dbName,@Part("rowCount")RequestBody rowCount); - - @Multipart - @POST("android/upload_person_name") - Call upload_person_name(@Part("dbName")RequestBody dbName,@Part("oldName")RequestBody oldName,@Part("newName")RequestBody newName); - - //Web서버에 이미지 전송 요청 - @Multipart - @POST("android/uploadimg") - Call uploadWebAddImage(@Part MultipartBody.Part image, @Query("dbName") String dbName); - - @Multipart - @POST("android/deleteimg") - Call uploadWebDeleteImage(@Part MultipartBody.Part filename, @Part("dbName")RequestBody dbName); - - - - -} diff --git a/app/src/main/java/com/example/metasearch/network/interfaces/CircleDataUploadCallbacks.java b/app/src/main/java/com/example/metasearch/network/interfaces/CircleDataUploadCallbacks.java deleted file mode 100644 index 50e1c7fb..00000000 --- a/app/src/main/java/com/example/metasearch/network/interfaces/CircleDataUploadCallbacks.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.example.metasearch.network.interfaces; - -import java.util.List; - -public interface CircleDataUploadCallbacks { - void onCircleUploadSuccess(List detectedObjects); - void onCircleUploadFailure(String message); -} \ No newline at end of file diff --git a/app/src/main/java/com/example/metasearch/network/interfaces/ImageAnalysisCompleteListener.java b/app/src/main/java/com/example/metasearch/network/interfaces/ImageAnalysisCompleteListener.java deleted file mode 100644 index adeb4dd7..00000000 --- a/app/src/main/java/com/example/metasearch/network/interfaces/ImageAnalysisCompleteListener.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.example.metasearch.network.interfaces; - -public interface ImageAnalysisCompleteListener { - void onImageAnalysisComplete(); -} diff --git a/app/src/main/java/com/example/metasearch/network/interfaces/Update.java b/app/src/main/java/com/example/metasearch/network/interfaces/Update.java deleted file mode 100644 index 66eec8a8..00000000 --- a/app/src/main/java/com/example/metasearch/network/interfaces/Update.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.example.metasearch.network.interfaces; - -public interface Update { - void performDataUpdate(); -} diff --git a/app/src/main/java/com/example/metasearch/network/interfaces/WebServerDeleteEntityCallbacks.java b/app/src/main/java/com/example/metasearch/network/interfaces/WebServerDeleteEntityCallbacks.java deleted file mode 100644 index 80d329c6..00000000 --- a/app/src/main/java/com/example/metasearch/network/interfaces/WebServerDeleteEntityCallbacks.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.example.metasearch.network.interfaces; - -public interface WebServerDeleteEntityCallbacks { - void onDeleteEntitySuccess(String message); - void onDeleteEntityFailure(String message); -} diff --git a/app/src/main/java/com/example/metasearch/network/interfaces/WebServerPersonDataUploadCallbacks.java b/app/src/main/java/com/example/metasearch/network/interfaces/WebServerPersonDataUploadCallbacks.java deleted file mode 100644 index cdf143ad..00000000 --- a/app/src/main/java/com/example/metasearch/network/interfaces/WebServerPersonDataUploadCallbacks.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.example.metasearch.network.interfaces; - -import java.util.List; - -public interface WebServerPersonDataUploadCallbacks { - void onPersonDataUploadSuccess(List photoNameResponse); - void onPersonDataUploadFailure(String message); -} \ No newline at end of file diff --git a/app/src/main/java/com/example/metasearch/network/interfaces/WebServerPersonFrequencyUploadCallbacks.java b/app/src/main/java/com/example/metasearch/network/interfaces/WebServerPersonFrequencyUploadCallbacks.java deleted file mode 100644 index adeac431..00000000 --- a/app/src/main/java/com/example/metasearch/network/interfaces/WebServerPersonFrequencyUploadCallbacks.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.example.metasearch.network.interfaces; - -import com.example.metasearch.network.response.PersonFrequencyResponse; - -public interface WebServerPersonFrequencyUploadCallbacks { - void onPersonFrequencyUploadSuccess(PersonFrequencyResponse responses); - void onPersonFrequencyUploadFailure(String message); -} \ No newline at end of file diff --git a/app/src/main/java/com/example/metasearch/network/interfaces/WebServerQueryCallbacks.java b/app/src/main/java/com/example/metasearch/network/interfaces/WebServerQueryCallbacks.java deleted file mode 100644 index 27abb54a..00000000 --- a/app/src/main/java/com/example/metasearch/network/interfaces/WebServerQueryCallbacks.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.example.metasearch.network.interfaces; - -import com.example.metasearch.network.response.PhotoNameResponse; - -public interface WebServerQueryCallbacks { - void onWebServerQuerySuccess(PhotoNameResponse photoNameResponse); - void onWebServerQueryFailure(); -} \ No newline at end of file diff --git a/app/src/main/java/com/example/metasearch/network/interfaces/WebServerUploadCallbacks.java b/app/src/main/java/com/example/metasearch/network/interfaces/WebServerUploadCallbacks.java deleted file mode 100644 index d7e06745..00000000 --- a/app/src/main/java/com/example/metasearch/network/interfaces/WebServerUploadCallbacks.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.example.metasearch.network.interfaces; - -import com.example.metasearch.network.response.PhotoResponse; - -public interface WebServerUploadCallbacks { - void onWebServerUploadSuccess(PhotoResponse detectedObjects); - void onWebServerUploadFailure(String message); -} diff --git a/app/src/main/java/com/example/metasearch/network/request/ChangeNameRequest.java b/app/src/main/java/com/example/metasearch/network/request/ChangeNameRequest.java deleted file mode 100644 index d3e868f0..00000000 --- a/app/src/main/java/com/example/metasearch/network/request/ChangeNameRequest.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.example.metasearch.network.request; - -public class ChangeNameRequest { - private String dbName; - private String oldName; - private String newName; - - public ChangeNameRequest(String dbName, String oldName, String newName) { - this.dbName = dbName; - this.oldName = oldName; - this.newName = newName; - } - - public String getDbName() { - return dbName; - } - - public void setDbName(String dbName) { - this.dbName = dbName; - } - - public String getOldName() { - return oldName; - } - - public void setOldName(String oldName) { - this.oldName = oldName; - } - - public String getNewName() { - return newName; - } - - public void setNewName(String newName) { - this.newName = newName; - } -} diff --git a/app/src/main/java/com/example/metasearch/network/request/DeleteEntityRequest.java b/app/src/main/java/com/example/metasearch/network/request/DeleteEntityRequest.java deleted file mode 100644 index a305566b..00000000 --- a/app/src/main/java/com/example/metasearch/network/request/DeleteEntityRequest.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.example.metasearch.network.request; - -public class DeleteEntityRequest { - private String dbName; - private String entityName; - public DeleteEntityRequest(String dbName, String entityName) { - this.dbName = dbName; - this.entityName = entityName; - } -} diff --git a/app/src/main/java/com/example/metasearch/network/request/NLQueryRequest.java b/app/src/main/java/com/example/metasearch/network/request/NLQueryRequest.java deleted file mode 100644 index ff1caf1c..00000000 --- a/app/src/main/java/com/example/metasearch/network/request/NLQueryRequest.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.example.metasearch.network.request; - -public class NLQueryRequest { - private String dbName; - private String query; - - public NLQueryRequest(String dbName, String query) { - this.dbName = dbName; - this.query = query; - } - - // Getters and Setters - public String getDbName() { - return dbName; - } - - public void setDbName(String dbName) { - this.dbName = dbName; - } - - public String getQuery() { - return query; - } - - public void setQuery(String query) { - this.query = query; - } -} diff --git a/app/src/main/java/com/example/metasearch/network/request/OpenAIRequest.java b/app/src/main/java/com/example/metasearch/network/request/OpenAIRequest.java deleted file mode 100644 index 0a300c1a..00000000 --- a/app/src/main/java/com/example/metasearch/network/request/OpenAIRequest.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.example.metasearch.network.request; - -import com.example.metasearch.data.model.Message; - -import java.util.List; - -public class OpenAIRequest { - private String model; - private List messages; - - public OpenAIRequest(String model, List messages) { - this.model = model; - this.messages = messages; - } - - public String getModel() { - return model; - } - - public void setModel(String model) { - this.model = model; - } - - public List getMessages() { - return messages; - } - - public void setMessages(List messages) { - this.messages = messages; - } -} diff --git a/app/src/main/java/com/example/metasearch/network/request/PersonFrequencyRequest.java b/app/src/main/java/com/example/metasearch/network/request/PersonFrequencyRequest.java deleted file mode 100644 index b00f600f..00000000 --- a/app/src/main/java/com/example/metasearch/network/request/PersonFrequencyRequest.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.example.metasearch.network.request; - -import java.util.List; - -public class PersonFrequencyRequest { - private String dbName; - private List personNames; - - public PersonFrequencyRequest(String dbName, List personNames) { - this.dbName = dbName; - this.personNames = personNames; - } - - public String getDbName() { - return dbName; - } - - public void setDbName(String dbName) { - this.dbName = dbName; - } - - public List getPersonNames() { - return personNames; - } - - public void setPersonNames(List personNames) { - this.personNames = personNames; - } -} diff --git a/app/src/main/java/com/example/metasearch/network/response/ChangeNameResponse.java b/app/src/main/java/com/example/metasearch/network/response/ChangeNameResponse.java deleted file mode 100644 index 0c6d5d2a..00000000 --- a/app/src/main/java/com/example/metasearch/network/response/ChangeNameResponse.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.example.metasearch.network.response; - -public class ChangeNameResponse { - private String message; - - public String getMessage() { - return message; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/metasearch/network/response/CircleDetectionResponse.java b/app/src/main/java/com/example/metasearch/network/response/CircleDetectionResponse.java deleted file mode 100644 index 6420bad2..00000000 --- a/app/src/main/java/com/example/metasearch/network/response/CircleDetectionResponse.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.example.metasearch.network.response; - -import java.util.List; - -public class CircleDetectionResponse { - private String message; - private List detected_objects; // 원에서 분석된 객체 이름 리스트 - - // Getters and Setters - public String getMessage() { - return message; - } - - public void setMessage(String message) { - this.message = message; - } - - public List getDetectedObjects() { - return detected_objects; - } - - public void setDetectedObjects(List detectedObjects) { - this.detected_objects = detectedObjects; - } -} diff --git a/app/src/main/java/com/example/metasearch/network/response/DeleteEntityResponse.java b/app/src/main/java/com/example/metasearch/network/response/DeleteEntityResponse.java deleted file mode 100644 index 543b7cdb..00000000 --- a/app/src/main/java/com/example/metasearch/network/response/DeleteEntityResponse.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.example.metasearch.network.response; - -public class DeleteEntityResponse { - private String message; - public String getMessage() { - return message; - } -} diff --git a/app/src/main/java/com/example/metasearch/network/response/OpenAIResponse.java b/app/src/main/java/com/example/metasearch/network/response/OpenAIResponse.java deleted file mode 100644 index 0a65a9d6..00000000 --- a/app/src/main/java/com/example/metasearch/network/response/OpenAIResponse.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.example.metasearch.network.response; - -import com.example.metasearch.data.model.Choice; - -import java.util.List; - -public class OpenAIResponse { - private List choices; - - public List getChoices() { - return choices; - } - - public void setChoices(List choices) { - this.choices = choices; - } -} diff --git a/app/src/main/java/com/example/metasearch/network/response/PersonFrequencyResponse.java b/app/src/main/java/com/example/metasearch/network/response/PersonFrequencyResponse.java deleted file mode 100644 index fc53fada..00000000 --- a/app/src/main/java/com/example/metasearch/network/response/PersonFrequencyResponse.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.example.metasearch.network.response; - -import java.util.List; - -public class PersonFrequencyResponse { - private List frequencies; - - public List getFrequencies() { - return frequencies; - } - - public void setFrequencies(List frequencies) { - this.frequencies = frequencies; - } - - public static class Frequency { - private String personName; - private int frequency; - - public String getPersonName() { - return personName; - } - - public void setPersonName(String personName) { - this.personName = personName; - } - - public int getFrequency() { - return frequency; - } - - public void setFrequency(int frequency) { - this.frequency = frequency; - } - } -} diff --git a/app/src/main/java/com/example/metasearch/network/response/PhotoNameResponse.java b/app/src/main/java/com/example/metasearch/network/response/PhotoNameResponse.java deleted file mode 100644 index 3a9e5091..00000000 --- a/app/src/main/java/com/example/metasearch/network/response/PhotoNameResponse.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.example.metasearch.network.response; - -import java.util.List; - -public class PhotoNameResponse { - private List PhotoName; - - public List getPhotoName() { - return PhotoName; - } - - public void setPhotoName(List photoName) { - PhotoName = photoName; - } -} diff --git a/app/src/main/java/com/example/metasearch/network/response/PhotoResponse.java b/app/src/main/java/com/example/metasearch/network/response/PhotoResponse.java deleted file mode 100644 index b6b2605f..00000000 --- a/app/src/main/java/com/example/metasearch/network/response/PhotoResponse.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.example.metasearch.network.response; - -import java.util.List; -import java.util.Map; - -public class PhotoResponse { - private Photos photos; - - public Photos getPhotos() { - return photos; - } - - public static class Photos { - private List commonPhotos; - private Map> individualPhotos; - - public List getCommonPhotos() { - return commonPhotos; - } - - public Map> getIndividualPhotos() { - return individualPhotos; - } - } -} diff --git a/app/src/main/java/com/example/metasearch/network/response/TripleResponse.java b/app/src/main/java/com/example/metasearch/network/response/TripleResponse.java deleted file mode 100644 index 942f44c7..00000000 --- a/app/src/main/java/com/example/metasearch/network/response/TripleResponse.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.example.metasearch.network.response; - -public class TripleResponse { - private String triple; - - public String getTriple() { - return triple; - } - - public void setTriple(String triple) { - this.triple = triple; - } -} diff --git a/app/src/main/java/com/example/metasearch/network/response/UploadResponse.java b/app/src/main/java/com/example/metasearch/network/response/UploadResponse.java deleted file mode 100644 index cda0ef33..00000000 --- a/app/src/main/java/com/example/metasearch/network/response/UploadResponse.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.example.metasearch.network.response; - -import java.util.List; - -public class UploadResponse { - - private List images; - - public List getImages() { - return images; - } - - public static class ImageData { - private String imageName; - private String imageBytes; - private String division; - private boolean isFaceExit; - - public String getImageName() { - return imageName; - } - - public String getImageBytes() { - return imageBytes; - } - public boolean getIsExit(){return isFaceExit;} - - public java.lang.String getDivision() { - return division; - } - } -} diff --git a/app/src/main/java/com/example/metasearch/ui/activity/CircleToSearchActivity.java b/app/src/main/java/com/example/metasearch/ui/activity/CircleToSearchActivity.java deleted file mode 100644 index 0184e22b..00000000 --- a/app/src/main/java/com/example/metasearch/ui/activity/CircleToSearchActivity.java +++ /dev/null @@ -1,351 +0,0 @@ -package com.example.metasearch.ui.activity; - -import static androidx.constraintlayout.helper.widget.MotionEffect.TAG; - -import android.content.Context; -import android.content.Intent; -import android.graphics.Typeface; -import android.net.Uri; -import android.os.Bundle; -import android.util.Log; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.FrameLayout; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; -import androidx.core.content.res.ResourcesCompat; -import androidx.recyclerview.widget.GridLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import com.bumptech.glide.Glide; -import com.example.metasearch.R; -import com.example.metasearch.data.dao.DatabaseHelper; -import com.example.metasearch.databinding.ActivityCircleToSearchBinding; -import com.example.metasearch.utils.DatabaseUtils; -import com.example.metasearch.network.interfaces.CircleDataUploadCallbacks; -import com.example.metasearch.network.interfaces.WebServerUploadCallbacks; -import com.example.metasearch.manager.AIRequestManager; -import com.example.metasearch.utils.GalleryImageManager; -import com.example.metasearch.manager.WebRequestManager; -import com.example.metasearch.data.model.Circle; -import com.example.metasearch.network.response.PhotoResponse; -import com.example.metasearch.ui.adapter.ImageAdapter; -import com.google.android.material.bottomnavigation.BottomNavigationView; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -import io.github.muddz.styleabletoast.StyleableToast; -import me.jfenn.colorpickerdialog.dialogs.ColorPickerDialog; -import me.jfenn.colorpickerdialog.interfaces.OnColorPickedListener; - -public class CircleToSearchActivity extends AppCompatActivity - implements ImageAdapter.OnImageClickListener, - CircleDataUploadCallbacks, - WebServerUploadCallbacks { - private ActivityCircleToSearchBinding binding; - private Uri imageUri; - private AIRequestManager aiRequestManager; - private WebRequestManager webRequestManager; - private Context context; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - StyleableToast.makeText(this, "드래그 해서 원을 그려주세요.", R.style.customToast).show(); - setupUI(); - setupListeners(); - aiRequestManager = AIRequestManager.getAiImageUploader(this); - webRequestManager = WebRequestManager.getWebImageUploader(); - } - - private void setupUI() { - binding = ActivityCircleToSearchBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - - imageUri = Uri.parse(getIntent().getStringExtra("imageUri")); - // Glide를 사용하여 이미지 로드 및 표시(이미지 자동 회전 방지) - Glide.with(this) - .load(imageUri) - .into(binding.customImageView); - - // 화면 크기의 1/2 높이로 CustomImageView 크기 조정 - int halfScreenHeight = getResources().getDisplayMetrics().heightPixels / 2; - ViewGroup.LayoutParams layoutParams = binding.customImageView.getLayoutParams(); - layoutParams.height = halfScreenHeight; - binding.customImageView.setLayoutParams(layoutParams); - } - - private void showColorPickerDialog() { - new ColorPickerDialog() - .withColor(getResources().getColor(R.color.white)) // 기본 색상 - .withListener(new OnColorPickedListener() { - @Override - public void onColorPicked(@Nullable ColorPickerDialog dialog, int color) { - // 선택한 색상 사용 - binding.customImageView.setCircleColor(color); - } - }) - .show(getSupportFragmentManager(), "colorPicker"); - } - - private void setupListeners() { - binding.circleMenu.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() { - @Override - public boolean onNavigationItemSelected(@NonNull MenuItem item) { - int itemId = item.getItemId(); - if (itemId == R.id.search) { - try { - // AI 서버로 이미지와 원 리스트 전송 - sendCirclesAndImage(); - } catch (IOException e) { - throw new RuntimeException(e); - } - return true; - } else if (itemId == R.id.reset) { - // 리셋 버튼 클릭 시 모든 원 삭제 - binding.customImageView.clearCircles(); - // 화면 초기화 - binding.individualPhotosContainer.removeAllViews(); - return true; - } else if (itemId == R.id.color) { - // 컬러 버튼 클릭 시 컬러 피커 다이얼로그 표시 - showColorPickerDialog(); - return true; - } - return false; - } - }); - } - - private void sendCirclesAndImage() throws IOException { - List circles = binding.customImageView.getCircles(); - if (imageUri != null && !circles.isEmpty()) { - // 검색 버튼 비활성화 - MenuItem searchItem = binding.circleMenu.getMenu().findItem(R.id.search); - searchItem.setEnabled(false); - // 요청 전에 로딩 아이콘 표시 - binding.spinKit.setVisibility(View.VISIBLE); - - // AI Server로 이미지와 원 리스트 전송 - aiRequestManager.uploadCircleData(imageUri, circles, DatabaseUtils.getPersistentDeviceDatabaseName(this), this, this); - } else { - StyleableToast.makeText(this, "이미지 또는 원 정보가 없습니다. 드래그 해서 원을 그려주세요.", R.style.customToast).show(); - // Individual photos 처리 - binding.individualPhotosContainer.removeAllViews(); - } - } - private void updateRecyclerViewWithResponse(PhotoResponse photoResponse) { - Set uniqueCommonPhotoNames = new HashSet<>(); - Map> individualPhotosMap = photoResponse.getPhotos().getIndividualPhotos(); - - // Common photos 처리 - if (photoResponse != null && photoResponse.getPhotos() != null) { - uniqueCommonPhotoNames.addAll(photoResponse.getPhotos().getCommonPhotos()); - } - List commonPhotoUris = GalleryImageManager.findMatchedUris(new ArrayList<>(uniqueCommonPhotoNames), this); - - // Individual photos 처리 - binding.individualPhotosContainer.removeAllViews(); - - // Common photos에 대한 카테고리 이름 수집 - Set relatedCategories = new HashSet<>(); - for (String commonPhoto : uniqueCommonPhotoNames) { - for (Map.Entry> entry : individualPhotosMap.entrySet()) { - if (entry.getValue().contains(commonPhoto)) { - relatedCategories.add(entry.getKey()); - } - } - } - - // Common photos 추가 - if (!commonPhotoUris.isEmpty()) { - // Common photos 카테고리 이름 출력 - StringBuilder categoriesText = new StringBuilder(); - for (String category : relatedCategories) { - categoriesText.append("#").append(category).append(" "); - } - TextView commonPhotosTextView = new TextView(this); - commonPhotosTextView.setText(categoriesText); - commonPhotosTextView.setTextSize(16); - Typeface customFont = ResourcesCompat.getFont(this, R.font.light); // 폰트 로드 - commonPhotosTextView.setTypeface(customFont, Typeface.BOLD); // 폰트와 스타일 적용 - - commonPhotosTextView.setPadding(16, 16, 16, 16); - binding.individualPhotosContainer.addView(commonPhotosTextView); - - // Common Photos RecyclerView 추가 - RecyclerView commonRecyclerView = new RecyclerView(this); - commonRecyclerView.setLayoutManager(new GridLayoutManager(this, 5)); - ImageAdapter commonAdapter = new ImageAdapter(commonPhotoUris, this, this); - commonRecyclerView.setAdapter(commonAdapter); - binding.individualPhotosContainer.addView(commonRecyclerView); - } - - // Common photos를 Individual photos에서 제거 - Set commonPhotoNames = new HashSet<>(uniqueCommonPhotoNames); - - for (Map.Entry> entry : individualPhotosMap.entrySet()) { - String category = entry.getKey(); - List photoNames = entry.getValue(); - // Common photos에 포함되지 않은 사진만 필터링 - List filteredPhotoNames = new ArrayList<>(); - for (String photoName : photoNames) { - if (!commonPhotoNames.contains(photoName)) { - filteredPhotoNames.add(photoName); - } - } - List photoUris = GalleryImageManager.findMatchedUris(filteredPhotoNames, this); - - if (!photoUris.isEmpty()) { - // Category TextView 추가 - TextView categoryTextView = new TextView(this); - categoryTextView.setText("#" + category); - categoryTextView.setTextSize(16); - Typeface customFont = ResourcesCompat.getFont(this, R.font.light); // 폰트 로드 - categoryTextView.setTypeface(customFont, Typeface.BOLD); // 폰트와 스타일 적용 - categoryTextView.setPadding(16, 16, 16, 16); - binding.individualPhotosContainer.addView(categoryTextView); - - // Photos RecyclerView 추가 - RecyclerView recyclerView = new RecyclerView(this); - recyclerView.setLayoutManager(new GridLayoutManager(this, 5)); - ImageAdapter adapter = new ImageAdapter(photoUris, this, this); - recyclerView.setAdapter(adapter); - binding.individualPhotosContainer.addView(recyclerView); - } - } - } - @Override - public void onImageClick(Uri uri) { - Intent intent = new Intent(this, ImageDisplayActivity.class); - intent.putExtra("imageUri", uri.toString()); - startActivity(intent); - } - @Override - public void onCircleUploadSuccess(List detectedObjects) { - runOnUiThread(() -> { - try { - MenuItem searchItem = binding.circleMenu.getMenu().findItem(R.id.search); - searchItem.setEnabled(true); // 검색 버튼 활성화 - binding.spinKit.setVisibility(View.GONE); // 로딩 아이콘 숨김 - - // 등록된 인물 이름으로 검색하기 위한 필터링 - List newDetectedObjects = new ArrayList<>(); - for (String imageName : detectedObjects) { - String inputName = DatabaseHelper.getInstance(context).getInputNameByImageName(imageName); - if (inputName != null) { - newDetectedObjects.add(inputName); - } else { - newDetectedObjects.add(imageName); // 매칭되지 않으면 원래 이름을 사용 - } - } - - // detectedObjects와 newDetectedObjects가 모두 빈 문자열로만 구성되어 있는지 확인 - boolean allEmpty = detectedObjects.stream().allMatch(String::isEmpty) - && newDetectedObjects.stream().allMatch(String::isEmpty); - - if (allEmpty || detectedObjects.isEmpty()) { - // 분석된 객체가 없는 경우 처리 - StyleableToast.makeText(this, "분석된 객체가 없습니다.", R.style.customToast).show(); - // Individual photos 처리 - binding.individualPhotosContainer.removeAllViews(); - return; - } - - // 원의 중심에 텍스트 설정 - for (int i = 0; i < newDetectedObjects.size(); i++) { - if (i < binding.customImageView.getCircles().size()) { - Circle circle = binding.customImageView.getCircles().get(i); - addTextViewAtCircleCenter(circle, newDetectedObjects.get(i)); - } - } - - // newDetectedObjects에서 빈 문자열을 제거한 리스트를 생성 - List validDetectedObjects = newDetectedObjects.stream() - .filter(name -> name != null && !name.trim().isEmpty() && !name.equals("")) - .collect(Collectors.toList()); - - // Web Server로 이미지 분석 결과 전송 - webRequestManager.sendDetectedObjectsToWebServer(validDetectedObjects, DatabaseUtils.getPersistentDeviceDatabaseName(this), this); - } catch (Exception e) { - Log.e(TAG, "Error updating UI: ", e); - StyleableToast.makeText(this, "분석된 객체가 없습니다.", R.style.customToast).show(); - // Individual photos 처리 - binding.individualPhotosContainer.removeAllViews(); - } - }); - } - @Override - public void onCircleUploadFailure(String message) { - runOnUiThread(() -> { - MenuItem searchItem = binding.circleMenu.getMenu().findItem(R.id.search); - searchItem.setEnabled(true); // 검색 버튼 비활성화 - binding.spinKit.setVisibility(View.GONE); // 로딩 아이콘 숨김 - StyleableToast.makeText(this, "Upload failed: " + message, R.style.customToast).show(); - }); - } - - @Override - public void onWebServerUploadSuccess(PhotoResponse photoResponse) { - MenuItem searchItem = binding.circleMenu.getMenu().findItem(R.id.search); - searchItem.setEnabled(true); // 검색 버튼 활성화 - binding.spinKit.setVisibility(View.GONE); // 로딩 아이콘 숨김 - updateRecyclerViewWithResponse(photoResponse); // 검색된 사진으로 화면 업데이트 - } - - @Override - public void onWebServerUploadFailure(String message) { - MenuItem searchItem = binding.circleMenu.getMenu().findItem(R.id.search); - searchItem.setEnabled(true); // 검색 버튼 활성화 - } - // 원의 중심에 텍스트뷰 추가 - private void addTextViewAtCircleCenter(Circle circle, String text) { - try { - // TextView 생성 - TextView textView = new TextView(this); - if (text.equals("") || text.trim().isEmpty()) { - textView.setText("객체 인식 실패"); - } else { - textView.setText(text); - } - textView.setTextSize(12); - textView.setTextColor(getResources().getColor(R.color.light_pink)); - textView.setBackgroundResource(R.drawable.rounded_button); - Typeface customFont = ResourcesCompat.getFont(this, R.font.light); // 폰트 로드 - textView.setTypeface(customFont, Typeface.BOLD); // 폰트와 스타일 적용 - - // TextView의 레이아웃 파라미터 설정 - FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( - FrameLayout.LayoutParams.WRAP_CONTENT, - FrameLayout.LayoutParams.WRAP_CONTENT - ); - - // TextView의 위치 설정 - float centerX = circle.getCenterX() * binding.customImageView.getWidth(); - float centerY = circle.getCenterY() * binding.customImageView.getHeight(); - textView.measure(0, 0); // 텍스트뷰의 실제 크기를 측정 - params.leftMargin = (int) (centerX - (textView.getMeasuredWidth() / 2)); - params.topMargin = (int) (centerY - (textView.getMeasuredHeight() / 2)); - - // TextView의 레이아웃 파라미터 적용 - textView.setLayoutParams(params); - - // TextView를 FrameLayout에 추가 - FrameLayout parent = findViewById(R.id.custom_image_container); - parent.addView(textView); - } catch (Exception e) { - Log.e(TAG, "Error adding TextView: ", e); - } - } - -} diff --git a/app/src/main/java/com/example/metasearch/ui/activity/GraphDisplayActivity.java b/app/src/main/java/com/example/metasearch/ui/activity/GraphDisplayActivity.java deleted file mode 100644 index 6a15e997..00000000 --- a/app/src/main/java/com/example/metasearch/ui/activity/GraphDisplayActivity.java +++ /dev/null @@ -1,175 +0,0 @@ -package com.example.metasearch.ui.activity; - -import static com.example.metasearch.utils.GalleryImageManager.findMatchedUri; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.net.Uri; -import android.net.http.SslError; -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; -import android.util.Log; -import android.webkit.JavascriptInterface; -import android.webkit.SslErrorHandler; -import android.webkit.WebView; -import android.webkit.WebViewClient; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.Toast; - -import androidx.appcompat.app.AppCompatActivity; - -import com.example.metasearch.R; -import com.example.metasearch.databinding.ActivityGraphDisplayBinding; -import com.example.metasearch.utils.DatabaseUtils; -import com.example.metasearch.utils.GalleryImageManager; - -import java.util.ArrayList; -import java.util.List; - -import io.github.muddz.styleabletoast.StyleableToast; - -public class GraphDisplayActivity extends AppCompatActivity { - private ActivityGraphDisplayBinding binding; - private String imageName; - private Uri imageUri = null; - private List imageViews = new ArrayList<>(); // ImageView 객체 저장 - private static final int MAX_IMAGES = 10; // 최대 이미지 수 - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - init(); - setWebView(); -// setRecyclerView(null); - } -// public void setRecyclerView(List imageUris) { -// // 갤러리의 모든 사진을 출력하는 세로 방향 RecyclerView 세팅 -// ImageAdapter adapter = new ImageAdapter(imageUris, this, null); -// binding.recyclerView.setAdapter(adapter) ; -// binding.recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)); -// } - private void init() { - binding = ActivityGraphDisplayBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - - // Intent에서 이미지 URI 추출 - String imageUriString = getIntent().getStringExtra("imageUri"); - if (imageUriString != null) { - imageUri = Uri.parse(imageUriString); - imageName = GalleryImageManager.getFileNameFromUri(this, imageUri); - } else { - StyleableToast.makeText(this, "이미지를 불러올 수 없습니다.", R.style.customToast).show(); - } - } - public void setWebView() { - binding.webView2.setWebViewClient(new WebViewClient() { - @Override - public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { - // SSL 에러가 발생해도 계속 진행 - handler.proceed(); - } - }); - binding.webView2.getSettings().setJavaScriptEnabled(true); //자바스크립트 실행을 허용. 이거 꼭 해줘야 화면 나타남 - binding.webView2.getSettings().setLoadWithOverviewMode(true); // WebView 화면크기에 맞추도록 설정 - setUseWideViewPort 와 같이 써야함 - binding.webView2.getSettings().setUseWideViewPort(true); // wide viewport 설정 - setLoadWithOverviewMode 와 같이 써야함 - - Log.d("IMAGENAME", "http://113.198.85.6/entityTripleGraph/" + - DatabaseUtils.getPersistentDeviceDatabaseName(this) + "/" + imageName); - // JavascriptInterface 추가 - binding.webView2.addJavascriptInterface(new WebAppInterface(this), "Android"); - - binding.webView2.loadUrl("http://113.198.85.6/entityTripleGraph/" + - DatabaseUtils.getPersistentDeviceDatabaseName(this) + "/" + imageName); - - } - private Handler handler = new Handler(Looper.getMainLooper()); - public class WebAppInterface { - Context mContext; - - WebAppInterface(Context c) { - mContext = c; - } - - // 자바스크립트에서 호출할 수 있는 메서드 - @JavascriptInterface - public void receivePhotoName(final String photoName) { - // 새 스레드에서 작업 실행 - new Thread(new Runnable() { - @Override - public void run() { - // 백그라운드 스레드에서 갤러리 이미지 이름들을 가져오기 - final Uri matchedUri = findMatchedUri(photoName, mContext); - // 메인 스레드에서 UI 업데이트 - handler.post(new Runnable() { - @Override - public void run() { - if (matchedUri != null) { - Log.d("FUNCTION", "CALL func"); - addImageToGallery(matchedUri); - } else { - @SuppressLint("ShowToast") Toast toast = Toast.makeText(mContext, "사진을 찾지 못했습니다", Toast.LENGTH_SHORT); - toast.show(); - - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - toast.cancel(); - } - }, 500); // 1000ms = 1초 후에 실행 - } - } - }); - } - }).start(); - } - } - private List existingUris = new ArrayList<>(); // 이미 추가된 이미지 URI 목록 - - private void addImageToGallery(Uri uri) { - if (existingUris.contains(uri)) { - Toast.makeText(this, "이미 추가된 사진입니다.", Toast.LENGTH_SHORT).show(); - return; // 이미 목록에 있는 URI이면 추가하지 않고 종료 - } - - ImageView imageView = new ImageView(this); - int sizeInPixels = dpToPx(300); // 이미지 크기를 픽셀 단위로 설정 - imageView.setLayoutParams(new LinearLayout.LayoutParams(sizeInPixels, sizeInPixels)); - imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); - imageView.setImageURI(uri); - - // 최대 이미지 수를 초과하면 가장 오래된 이미지 제거 - if (imageViews.size() >= MAX_IMAGES) { - ImageView oldestView = imageViews.remove(imageViews.size() - 1); - binding.imageContainer.removeView(oldestView); - existingUris.remove(existingUris.size() - 1); // 가장 오래된 URI도 제거 - } - - // 새 이미지 및 URI 추가 - imageViews.add(0, imageView); - binding.imageContainer.addView(imageView, 0); - existingUris.add(0, uri); // 새 URI를 리스트의 맨 앞에 추가 - -// imageView.setOnClickListener(new View.OnClickListener() { -// @Override -// public void onClick(View v) { -// Intent intent = new Intent(requireContext(), GraphDisplayActivity.class); -// intent.putExtra("imageUri", uri.toString()); -// startActivity(intent); -// } -// }); - } - -// private void setImageToPhotoView(Uri imageUri) { -// // Glide를 사용하여 PhotoView에 이미지 설정 -// Glide.with(this) -// .load(imageUri) -// .into(binding.imageView2); -// } - // dp를 픽셀로 변환하는 유틸리티 메소드 - private int dpToPx(int dp) { - return (int) (dp * getResources().getDisplayMetrics().density); - } - -} diff --git a/app/src/main/java/com/example/metasearch/ui/activity/ImageDisplayActivity.java b/app/src/main/java/com/example/metasearch/ui/activity/ImageDisplayActivity.java deleted file mode 100644 index 732d2753..00000000 --- a/app/src/main/java/com/example/metasearch/ui/activity/ImageDisplayActivity.java +++ /dev/null @@ -1,179 +0,0 @@ -package com.example.metasearch.ui.activity; - -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.util.Log; - -import androidx.appcompat.app.AppCompatActivity; - -import com.bumptech.glide.Glide; -import com.example.metasearch.R; -import com.example.metasearch.databinding.ActivityImageDisplayBinding; -import com.example.metasearch.utils.DatabaseUtils; -import com.example.metasearch.manager.ChatGPTManager; -import com.example.metasearch.utils.GalleryImageManager; -import com.example.metasearch.manager.WebRequestManager; -import com.example.metasearch.data.model.Message; -import com.example.metasearch.network.response.OpenAIResponse; -import com.example.metasearch.network.response.TripleResponse; - -import java.util.ArrayList; -import java.util.List; - -import io.github.muddz.styleabletoast.StyleableToast; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -public class ImageDisplayActivity extends AppCompatActivity { - private ActivityImageDisplayBinding binding; - private String imageUriString; - private WebRequestManager webRequestManager; // 웹 요청 관리자 추가 - private ChatGPTManager chatGPTManager; - private static final String TEXT_VIEW_TAG = "descriptionTextView"; - - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - binding = ActivityImageDisplayBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - - init(); - if (imageUriString != null) { - Uri imageUri = Uri.parse(imageUriString); - setImageToPhotoView(imageUri); - } else { - StyleableToast.makeText(this, "이미지를 불러올 수 없습니다.", R.style.customToast).show(); - } - setListeners(); - } - private void init() { - webRequestManager = WebRequestManager.getWebImageUploader(); - // Intent에서 이미지 URI 추출 - imageUriString = getIntent().getStringExtra("imageUri"); - chatGPTManager = ChatGPTManager.getInstance(); - } - public void setListeners() { - // BottomNavigationView 리스너 설정 - binding.imageMenu.setOnNavigationItemSelectedListener(item -> { - if (item.getItemId() == R.id.comment) { - // 챗 지피티 사용해 이미지 설명 구현 - // 사진 위에 설명 텍스트를 바로 띄움 - fetchAndDisplayTripleData(); // csv 데이터 요청 및 표시 - return false; - } else if (item.getItemId() == R.id.graph) { - // 그래프 액티비티 실행 - startGraphActivity(); - } else if (item.getItemId() == R.id.search) { - // 써클 투 써치 액티비티 실행 - startCircleToSearchActivity(); - } else if (item.getItemId() == R.id.share) { - // 사진 공유 기능 동작 - shareImage(); - return false; - } - return true; - }); - } - private void fetchAndDisplayTripleData() { - Uri imageUri = Uri.parse(imageUriString); - String photoName = GalleryImageManager.getFileNameFromUri(this, imageUri); // 파일 이름 추출 - - Log.d("PHOTONAME",photoName); - webRequestManager.fetchTripleData( - DatabaseUtils.getPersistentDeviceDatabaseName(this), - photoName, - new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful() && response.body() != null) { - String tripleData = response.body().getTriple(); - // UI 스레드에서 UI 업데이트 실행 - runOnUiThread(() -> { - displayTripleData(tripleData); - }); - } else { - runOnUiThread(() -> { - StyleableToast.makeText(ImageDisplayActivity.this, "Failed to load triple data.", R.style.customToast).show(); - }); - } - } - @Override - public void onFailure(Call call, Throwable t) { - runOnUiThread(() -> { - StyleableToast.makeText(ImageDisplayActivity.this, "Error: " + t.getMessage(), R.style.customToast).show(); - }); - } - }); - } - private void displayTripleData(String tripleData) { - List messages = createMessagesFromTripleData(tripleData); - chatGPTManager.getChatResponse(messages, 150, new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { - OpenAIResponse openAIResponse = response.body(); - if (openAIResponse != null && !openAIResponse.getChoices().isEmpty()) { - // Choice 객체에서 Message 객체를 가져오고, Message 객체의 내용(content)을 추출 - Message message = openAIResponse.getChoices().get(0).getMessage(); - if (message != null) { - String responseText = message.getContent(); - runOnUiThread(() -> addTextViewOnImage(responseText)); - Log.d("GPT", "Response: " + responseText); - } else { - runOnUiThread(() -> StyleableToast.makeText(ImageDisplayActivity.this, "No message found in response.", R.style.customToast).show()); - } - } else { - runOnUiThread(() -> StyleableToast.makeText(ImageDisplayActivity.this, "No choices found in response.", R.style.customToast).show()); - } - } else { - runOnUiThread(() -> StyleableToast.makeText(ImageDisplayActivity.this, "Failed to get response from ChatGPT.", R.style.customToast).show()); - Log.e("GPT", "API Response Error: " + response.errorBody()); - } - } - @Override - public void onFailure(Call call, Throwable t) { - runOnUiThread(() -> StyleableToast.makeText(ImageDisplayActivity.this, "Error: " + t.getMessage(), R.style.customToast).show()); - Log.e("GPT", "API Call Failure: " + t); - } - }); - } - private void addTextViewOnImage(String text) { - binding.tripleDataTextView.setText(text); - binding.tripleDataTextView.setTextSize(16); - binding.tripleDataTextView.setTextColor(getResources().getColor(R.color.light_pink)); // 글자색 설정 -// binding.tripleDataTextView.setBackgroundResource(R.drawable.rounded_button); // 배경 리소스 설정 - } - - private List createMessagesFromTripleData(String tripleData) { - List messages = new ArrayList<>(); - messages.add(new Message("user", tripleData + " 이건 사진의 트리플이야. 반드시 이 내용만 사용해서 어떤 사진인지 설명해. ")); // 트리플 데이터를 그대로 메시지에 추가 - return messages; - } - - private void startGraphActivity() { - Intent intent = new Intent(this, GraphDisplayActivity.class); - intent.putExtra("imageUri", imageUriString); - startActivity(intent); - } - private void startCircleToSearchActivity() { - Intent intent = new Intent(this, CircleToSearchActivity.class); - intent.putExtra("imageUri", imageUriString); - startActivity(intent); - } - private void shareImage() { - Intent shareIntent = new Intent(Intent.ACTION_SEND); - shareIntent.putExtra(Intent.EXTRA_STREAM, imageUriString); - shareIntent.setType("image/*"); - startActivity(Intent.createChooser(shareIntent, "사진 공유하기")); - } - private void setImageToPhotoView(Uri imageUri) { - // Glide를 사용하여 PhotoView에 이미지 설정 - Glide.with(this) - .load(imageUri) - .into(binding.imageViewFullScreen); - } -} diff --git a/app/src/main/java/com/example/metasearch/ui/activity/MainActivity.java b/app/src/main/java/com/example/metasearch/ui/activity/MainActivity.java deleted file mode 100644 index fe4f5b97..00000000 --- a/app/src/main/java/com/example/metasearch/ui/activity/MainActivity.java +++ /dev/null @@ -1,189 +0,0 @@ -package com.example.metasearch.ui.activity; - -import android.Manifest; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.pm.PackageManager; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; - -import com.example.metasearch.R; - -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; -import androidx.annotation.NonNull; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.app.AppCompatActivity; -import androidx.core.content.ContextCompat; -import androidx.fragment.app.Fragment; -import androidx.navigation.NavController; -import androidx.navigation.Navigation; -import androidx.navigation.ui.AppBarConfiguration; -import androidx.navigation.ui.NavigationUI; - -import com.example.metasearch.databinding.ActivityMainBinding; -import com.example.metasearch.network.interfaces.Update; -import com.example.metasearch.ui.fragment.HomeFragment; - -public class MainActivity extends AppCompatActivity { - private static final int PERMISSIONS_REQUEST_READ_PHOTOS = 101; - private ActivityMainBinding binding; - private int currentSelectedItemId; // 현재 선택된 아이템 ID 저장 - - private final ActivityResultLauncher requestPermissionLauncher = - registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), result -> { - boolean allGranted = true; - for (Boolean granted : result.values()) { - allGranted &= granted; - } - if (allGranted) { - loadAllImagesInHomeFragment(); - } else { - showPermissionDeniedDialog(); - } - }); - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - binding = ActivityMainBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - - setupNavigation(); - checkAndRequestPermissions(); // 권한 요청 - } - - private void checkAndRequestPermissions() { - // Android 13(Tiramisu) 이상에서는 READ_MEDIA_IMAGES 권한 사용 - String storagePermission = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU ? - Manifest.permission.READ_MEDIA_IMAGES : Manifest.permission.READ_EXTERNAL_STORAGE; - - // 요청할 권한 목록에 알림 권한과 위치 정보 권한 추가 - String[] allPermissions = new String[] { - storagePermission, - Manifest.permission.ACCESS_FINE_LOCATION, // 위치 정보 권한 - Manifest.permission.ACCESS_COARSE_LOCATION, // 추가적으로 요청 가능 - Manifest.permission.READ_CALL_LOG, - Manifest.permission.WRITE_CALL_LOG, - Manifest.permission.POST_NOTIFICATIONS // 알림 권한 - }; - - String[] essentialPermissions = new String[] { - storagePermission - }; - - if (isFirstRun()) { - requestPermissionLauncher.launch(allPermissions); - } else if (!isPermissionGranted(storagePermission)) { - requestPermissionLauncher.launch(essentialPermissions); - } else { - loadAllImagesInHomeFragment(); - } - } - - private boolean isFirstRun() { - SharedPreferences prefs = getSharedPreferences("AppPrefs", MODE_PRIVATE); - boolean isFirstRun = prefs.getBoolean("isFirstRun", true); - if (isFirstRun) { - prefs.edit().putBoolean("isFirstRun", false).apply(); - } - return isFirstRun; - } - - private boolean isPermissionGranted(String permission) { - return ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED; - } - - private void showPermissionDeniedDialog() { - if (!isPermissionGranted(Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU ? - Manifest.permission.READ_MEDIA_IMAGES : Manifest.permission.READ_EXTERNAL_STORAGE)) { - new AlertDialog.Builder(this, R.style.CustomAlertDialogTheme) - .setTitle("권한 요청") - .setMessage("사진 권한이 필요합니다. 앱 설정에서 권한을 허용해주세요.") - .setPositiveButton("설정으로 이동", (dialog, which) -> openAppSettings()) - .setNegativeButton("취소", null) - .show(); - } - } - - private void openAppSettings() { - Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS); - Uri uri = Uri.fromParts("package", getPackageName(), null); - intent.setData(uri); - startActivity(intent); - } - - private void setupNavigation() { - NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_activity_main); - AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder( - R.id.navigation_person, R.id.navigation_home, R.id.navigation_search, R.id.navigation_graph - ).build(); - NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration); - NavigationUI.setupWithNavController(binding.navView, navController); - - // 최초 선택 아이템 설정 - currentSelectedItemId = binding.navView.getSelectedItemId(); - - binding.navView.setOnNavigationItemSelectedListener(item -> { - if (item.getItemId() == R.id.refresh) { - refreshData(); - // 'refresh' 선택 시 시각적 변화 없이 기능만 실행, 선택 상태 변화 방지 - return false; // false를 반환하여 아이템 선택 상태를 변경하지 않음 - } - // 나머지 아이템 선택 시 정상적인 네비게이션 처리 - boolean handled = NavigationUI.onNavDestinationSelected(item, navController); - if (handled) { - currentSelectedItemId = item.getItemId(); // 선택된 아이템 ID 업데이트 - } - return handled; - }); - } - - private void refreshData() { - Fragment navHostFragment = getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment_activity_main); - if (navHostFragment != null) { - Fragment currentFragment = navHostFragment.getChildFragmentManager().getPrimaryNavigationFragment(); - if (currentFragment instanceof Update) { - ((Update) currentFragment).performDataUpdate(); - } - } - } - - // 하단의 네비 바 숨김 - public void hideBottomNavigationView() { - binding.navView.animate().translationY(binding.navView.getHeight()); - } - - // 하단의 네비 바 보임 - public void showBottomNavigationView() { - binding.navView.animate().translationY(0); - } - - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - if (requestCode == PERMISSIONS_REQUEST_READ_PHOTOS && grantResults.length > 0) { - boolean allGranted = true; - for (int grantResult : grantResults) { - allGranted &= (grantResult == PackageManager.PERMISSION_GRANTED); - } - if (allGranted) { - loadAllImagesInHomeFragment(); - } else { - showPermissionDeniedDialog(); - } - } - } - - // HomeFragment에 접근하여 loadAllImages 호출 - private void loadAllImagesInHomeFragment() { - Fragment navHostFragment = getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment_activity_main); - if (navHostFragment != null) { - Fragment currentFragment = navHostFragment.getChildFragmentManager().getPrimaryNavigationFragment(); - if (currentFragment instanceof HomeFragment) { - ((HomeFragment) currentFragment).loadAllGalleryImages(); - } - } - } -} diff --git a/app/src/main/java/com/example/metasearch/ui/activity/PersonPhotosActivity.java b/app/src/main/java/com/example/metasearch/ui/activity/PersonPhotosActivity.java deleted file mode 100644 index 669c87ad..00000000 --- a/app/src/main/java/com/example/metasearch/ui/activity/PersonPhotosActivity.java +++ /dev/null @@ -1,263 +0,0 @@ -package com.example.metasearch.ui.activity; - -import android.content.Intent; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.net.Uri; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.CheckBox; -import android.widget.EditText; - -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.app.AppCompatActivity; -import androidx.lifecycle.ViewModelProvider; -import androidx.recyclerview.widget.GridLayoutManager; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import com.example.metasearch.R; -import com.example.metasearch.data.dao.DatabaseHelper; -import com.example.metasearch.databinding.ActivityPersonPhotosBinding; -import com.example.metasearch.utils.DatabaseUtils; -import com.example.metasearch.network.interfaces.WebServerPersonDataUploadCallbacks; -import com.example.metasearch.utils.GalleryImageManager; -import com.example.metasearch.manager.WebRequestManager; -import com.example.metasearch.data.model.Person; -import com.example.metasearch.ui.adapter.ImageAdapter; -import com.example.metasearch.ui.adapter.ImageSelectionAdapter; -import com.example.metasearch.ui.viewmodel.ImageViewModel; - -import java.util.ArrayList; -import java.util.List; - -import io.github.muddz.styleabletoast.StyleableToast; - -public class PersonPhotosActivity extends AppCompatActivity - implements WebServerPersonDataUploadCallbacks, - ImageAdapter.OnImageClickListener { - private ImageViewModel imageViewModel; - private WebRequestManager webRequestManager; - private ActivityPersonPhotosBinding binding; - private Integer id; - private String inputName; - private byte[] imageData; // 인물 얼굴 사진 - private byte[] thumbnailData; - private DatabaseHelper databaseHelper; - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - binding = ActivityPersonPhotosBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - - init(); - setupUI(); - setupListeners(); // 인물 정보 수정 버튼 - loadImages(); // 리사이클러뷰에 관련 인물 사진 모두 출력 - - } - private void init() { - webRequestManager = WebRequestManager.getWebImageUploader(); - databaseHelper = DatabaseHelper.getInstance(this); - - id = getIntent().getIntExtra("person_id", -1); - if (id != -1) { - Person person = databaseHelper.getPersonById(id); - if (person != null) { - inputName = person.getInputName(); - imageData = person.getImage(); - thumbnailData = person.getThumbnailImage(); - } - } - } - private void setupUI() { - // 화면 상단에 인물 이름 출력 - binding.personName.setText(inputName); - // 썸네일 데이터가 있는 경우 썸네일을 사용, 그렇지 않으면 전체 이미지를 사용 - byte[] displayImageData = (thumbnailData != null && thumbnailData.length > 0) ? thumbnailData : imageData; - - if (displayImageData != null && displayImageData.length > 0) { - Bitmap imageBitmap = BitmapFactory.decodeByteArray(displayImageData, 0, displayImageData.length); - binding.face.setImageBitmap(imageBitmap); - } else { - binding.face.setImageResource(R.drawable.ic_launcher_foreground); // 기본 이미지 설정 - } - setupRecyclerView(); - } - private void loadImages() { - if (inputName != null) { - webRequestManager.sendPersonData(inputName, DatabaseUtils.getPersistentDeviceDatabaseName(this), this); - } - } - private void setupRecyclerView() { - ImageAdapter adapter = new ImageAdapter(new ArrayList<>(), this, this); - binding.recyclerViewPerson.setLayoutManager(new GridLayoutManager(this, 5)); - binding.recyclerViewPerson.setAdapter(adapter); - } - private void setupListeners() { - // 인물 정보 수정 버튼 - binding.editbtn.setOnClickListener(v -> showEditPersonDialog()); - // 인물 얼굴 클릭 시 썸네일 변경 - binding.face.setOnClickListener(v -> showImageSelectionDialog()); - } - // 썸네일 변경하는 다이얼로그 - private void showImageSelectionDialog() { - List images = databaseHelper.getImagesByName(inputName); - if (images.isEmpty()) { - StyleableToast.makeText(this, "해당 인물의 이미지가 없습니다.", R.style.customToast).show(); - return; - } - - AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.CustomAlertDialogTheme); - LayoutInflater inflater = getLayoutInflater(); - View dialogView = inflater.inflate(R.layout.dialog_image_selection, null); - RecyclerView recyclerView = dialogView.findViewById(R.id.recyclerView); - - List bitmaps = new ArrayList<>(); - for (byte[] image : images) { - Bitmap bitmap = BitmapFactory.decodeByteArray(image, 0, image.length); - bitmaps.add(bitmap); - } - - // AlertDialog 변수 선언 - final AlertDialog dialog = builder.create(); - - ImageSelectionAdapter adapter = new ImageSelectionAdapter(this, bitmaps, bitmap -> { - updateThumbnailImage(bitmap); - dialog.dismiss(); // 다이얼로그 닫기 - }); - - recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)); - recyclerView.setAdapter(adapter); - - builder.setView(dialogView) -// .setTitle("이미지 선택") - .setNegativeButton("취소", (dialogInterface, which) -> dialog.dismiss()); - - dialog.setView(dialogView); - dialog.show(); - } - // 이름이 같은 모든 사람의 썸네일 이미지를 업데이트 하는 메서드 - private void updateThumbnailImage(Bitmap bitmap) { - if (bitmap != null) { - // 썸네일 이미지 변경 (기존 IMAGE 필드는 변경하지 않음) - binding.face.setImageBitmap(bitmap); - - // 새로운 썸네일 이미지를 데이터베이스에 저장 (여기서는 이름을 기준으로 저장) - boolean updateSuccess = databaseHelper.updateThumbnailImageByName(inputName, GalleryImageManager.getBytes(bitmap)); - if (updateSuccess) { - StyleableToast.makeText(this, "프로필 사진이 변경되었습니다.", R.style.customToast).show(); - } else { - StyleableToast.makeText(this, "프로필 사진 변경 실패.", R.style.customToast).show(); - } - } - } - private void showEditPersonDialog() { - AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.CustomAlertDialogTheme); - LayoutInflater inflater = getLayoutInflater(); - View dialogView = inflater.inflate(R.layout.dialog_edit_person, null); - - EditText editPersonName = dialogView.findViewById(R.id.editPersonName); - EditText editPhoneNumber = dialogView.findViewById(R.id.editPhoneNumber); - CheckBox checkBoxHomeDisplay = dialogView.findViewById(R.id.checkBoxHomeDisplay); // 체크박스 찾기 - - editPersonName.setText(inputName); - editPhoneNumber.setText(databaseHelper.getPhoneNumberById(id)); - - checkBoxHomeDisplay.setChecked(databaseHelper.getHomeDisplayById(id)); // 체크박스 상태 설정 - - builder.setView(dialogView) - .setTitle("인물 정보 수정") - .setPositiveButton("저장", null) - .setNegativeButton("취소", (dialog, which) -> dialog.dismiss()); - - AlertDialog dialog = builder.create(); - dialog.show(); - - dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> { - String newPersonName = editPersonName.getText().toString(); - String newPhoneNumber = editPhoneNumber.getText().toString(); - boolean newHomeDisplay = checkBoxHomeDisplay.isChecked(); // 새로운 체크 상태 가져오기 - - // 이름 중복 검사 - if (!newPersonName.isEmpty() && databaseHelper.isNameExists(newPersonName) && !newPersonName.equals(inputName)) { - new AlertDialog.Builder(this, R.style.CustomAlertDialogTheme) - .setTitle("이름 중복") - .setMessage("이미 존재하는 이름 입니다. 그래도 저장하시겠습니까?") - .setPositiveButton("예", (dialogInterface, i) -> { - - updatePersonInfo(newPersonName, newPhoneNumber, newHomeDisplay); - dialog.dismiss(); - - }) - .setNegativeButton("아니요", null) - .show(); - } else { - - // 이름 중복이 없거나 입력하지 않은 경우 - updatePersonInfo(newPersonName, newPhoneNumber, newHomeDisplay); - - dialog.dismiss(); - } - }); - } - - private void updatePersonInfo(String newName, String newPhone, boolean newHomeDisplay) { - boolean updateSuccess = databaseHelper.updatePersonByName(inputName, newName, newPhone, newHomeDisplay); - - if (updateSuccess) { - StyleableToast.makeText(this, "인물 정보가 저장되었습니다.", R.style.customToast).show(); - webRequestManager.changePersonName(DatabaseUtils.getPersistentDeviceDatabaseName(this), inputName, newName); - inputName = newName; - binding.personName.setText(newName); - } else { - StyleableToast.makeText(this, "인물 정보 업데이트를 종료합니다.", R.style.customToast).show(); - } - } - - private void updateUIWithMatchedUris(List matchedUris) { - if (imageViewModel == null) { - imageViewModel = new ViewModelProvider(this).get(ImageViewModel.class); - } - imageViewModel.setImageUris(matchedUris); - - if (!matchedUris.isEmpty()) { - ImageAdapter adapter = (ImageAdapter) binding.recyclerViewPerson.getAdapter(); - if (adapter != null) { - adapter.updateData(matchedUris); - } - } else { - StyleableToast.makeText(this, "관련 사진이 없습니다.", R.style.customToast).show(); - } - } - - private void updateRecyclerViewWithResponse(List photoResponse) { - List matchedUris = new ArrayList<>(); - if (photoResponse != null) { - matchedUris = GalleryImageManager.findMatchedUris(photoResponse, this); - } - - ImageAdapter imageAdapter = new ImageAdapter(matchedUris, this, this); - binding.recyclerViewPerson.setAdapter(imageAdapter); - - updateUIWithMatchedUris(matchedUris); - } - - @Override - public void onPersonDataUploadSuccess(List personImages) { - runOnUiThread(() -> updateRecyclerViewWithResponse(personImages)); - } - - @Override - public void onPersonDataUploadFailure(String message) { - } - - @Override - public void onImageClick(Uri uri) { - Intent intent = new Intent(this, ImageDisplayActivity.class); - intent.putExtra("imageUri", uri.toString()); - startActivity(intent); - } -} diff --git a/app/src/main/java/com/example/metasearch/ui/adapter/CustomArrayAdapter.java b/app/src/main/java/com/example/metasearch/ui/adapter/CustomArrayAdapter.java deleted file mode 100644 index fd275b96..00000000 --- a/app/src/main/java/com/example/metasearch/ui/adapter/CustomArrayAdapter.java +++ /dev/null @@ -1,134 +0,0 @@ -package com.example.metasearch.ui.adapter; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.text.Spannable; -import android.text.SpannableString; -import android.text.style.ForegroundColorSpan; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.Filter; -import android.widget.Filterable; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; - -import com.example.metasearch.R; - -import java.util.ArrayList; -import java.util.List; - -// 검색어 자동 완성 기능에 필요한 데이터 관리 클래스 -public class CustomArrayAdapter extends ArrayAdapter implements Filterable { - private final int resourceLayout; - private final Context mContext; - private Filter filter; // 필터링을 위한 필터 객체 - private final List originalList; // 필터링 되기 전의 원본 아이템 리스트(추천 단어 리스트) - private String userInputText = ""; - - public CustomArrayAdapter(@NonNull Context context, int resource, @NonNull List items) { - super(context, resource, items); - this.resourceLayout = resource; - this.mContext = context; - this.originalList = new ArrayList<>(items); - } - - // 각 아이템의 뷰를 생성하여 반환 - @SuppressLint("ResourceAsColor") - @NonNull - @Override - public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { - // 뷰가 재활용되지 않는 경우, 레이아웃 인플레이터를 사용하여 뷰 생성 - if (convertView == null) { - LayoutInflater inflater = LayoutInflater.from(mContext); - convertView = inflater.inflate(resourceLayout, parent, false); - } - - // 현재 포지션의 아이템 - String item = getItem(position); - if (item != null) { - // 아이템의 텍스트 설정 - TextView textView = convertView.findViewById(R.id.text); - - // 사용자가 입력한 마지막 단어 - String[] words = userInputText.split("\\s+"); - String lastWord = words[words.length - 1]; - - int startPos = item.toLowerCase().indexOf(lastWord.toLowerCase()); - if (startPos != -1) { - int endPos = startPos + lastWord.length(); - - SpannableString spannableString = new SpannableString(item); - // colors.xml에서 정의된 색상을 사용 - int highlightColor = ContextCompat.getColor(mContext, R.color.white); - spannableString.setSpan(new ForegroundColorSpan(highlightColor), startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - - textView.setText(spannableString); - } else { - textView.setText(item); - } - } - return convertView; - } - - public void setUserInputText(String text) { - userInputText = text; - // 필터링 강제 실행 - getFilter().filter(text); - } - - // 필터 객체 반환 - @NonNull - @Override - public Filter getFilter() { - if (filter == null) { - filter = new Filter() { - // performFiltering 메서드는 백그라운드 스레드에서 실행되어 필터링 작업을 수행 - @Override - protected FilterResults performFiltering(CharSequence constraint) { - FilterResults results = new FilterResults(); - - // 입력된 텍스트가 있을 경우 필터링 수행 - if (constraint != null && constraint.length() > 0) { - String searchPattern = constraint.toString().toLowerCase().trim(); - List filteredList = new ArrayList<>(); - - // 마지막 입력된 단어를 기준으로 필터링 - String[] words = searchPattern.split("\\s+"); // 공백으로 분리 - String lastWord = words[words.length - 1]; // 마지막 단어 추출 - - // 마지막 단어로 시작하는 아이템만 필터링하여 리스트에 추가 - for (String item : originalList) { - if (item.toLowerCase().startsWith(lastWord)) { - filteredList.add(item); - } - } - - results.values = filteredList; - results.count = filteredList.size(); - } - return results; - } - - // publishResults 메서드는 UI 스레드에서 실행되어 필터링 결과 반영 - @SuppressWarnings("unchecked") - @Override - protected void publishResults(CharSequence constraint, FilterResults results) { - clear(); // 필터링 결과를 반영하기 전에 리스트 클리어 - if (results != null && results.count > 0) { - // 필터링 결과가 있으면 리스트에 추가 - addAll((List) results.values); - } - // 데이터가 변경된 것을 알려 리스트 갱신 - notifyDataSetChanged(); - } - }; - } - return filter; // 필터 객체 반환 - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/example/metasearch/ui/adapter/ImageAdapter.java b/app/src/main/java/com/example/metasearch/ui/adapter/ImageAdapter.java deleted file mode 100644 index a77a56fe..00000000 --- a/app/src/main/java/com/example/metasearch/ui/adapter/ImageAdapter.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.example.metasearch.ui.adapter; - -import android.content.Context; -import android.net.Uri; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; - -import com.bumptech.glide.Glide; -import com.example.metasearch.R; - -import java.util.ArrayList; -import java.util.List; - -public class ImageAdapter extends RecyclerView.Adapter { - private List imageUris; - private Context context; - private OnImageClickListener listener; - - public ImageAdapter(List imageUris, Context context, OnImageClickListener listener) { - this.imageUris = (imageUris != null) ? imageUris : new ArrayList<>(); // null 체크 추가 - this.context = context; - this.listener = listener; - } - - // 데이터 업데이트 메소드 - public void updateData(List uris) { - this.imageUris = uris; // 새로운 URI 리스트로 업데이트 - notifyDataSetChanged(); // RecyclerView를 갱신 - } - - // 이미지 클릭 리스너 인터페이스 정의 - public interface OnImageClickListener { - void onImageClick(Uri uri); - } - - @NonNull - @Override - public ImageViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.photo_item, parent, false); - return new ImageViewHolder(view); - } - - @Override - public void onBindViewHolder(ImageViewHolder holder, int position) { - Uri imageUri = imageUris.get(position); -// holder.imageView.setImageURI(imageUri); - // Glide를 사용하여 이미지 로드 및 표시(속도 향상) - Glide.with(context) - .load(imageUri) - .into(holder.imageView); - - // 클릭 이벤트 설정 - holder.imageView.setOnClickListener(v -> { - if (imageUri != null) { - listener.onImageClick(imageUri); - } - }); - - // 이미지 뷰의 크기를 조절 - int screenWidth = context.getResources().getDisplayMetrics().widthPixels; - int imageSize = screenWidth / 5; - - ViewGroup.LayoutParams layoutParams = holder.imageView.getLayoutParams(); - layoutParams.width = imageSize; - layoutParams.height = imageSize; - holder.imageView.setLayoutParams(layoutParams); - } - - @Override - public int getItemCount() { - return imageUris.size(); - } - - public static class ImageViewHolder extends RecyclerView.ViewHolder { - public ImageView imageView; - - public ImageViewHolder(View itemView) { - super(itemView); - imageView = itemView.findViewById(R.id.imageView); - } - } -} diff --git a/app/src/main/java/com/example/metasearch/ui/adapter/ImageSelectionAdapter.java b/app/src/main/java/com/example/metasearch/ui/adapter/ImageSelectionAdapter.java deleted file mode 100644 index 7233033f..00000000 --- a/app/src/main/java/com/example/metasearch/ui/adapter/ImageSelectionAdapter.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.example.metasearch.ui.adapter; - -import android.content.Context; -import android.graphics.Bitmap; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; - -import com.example.metasearch.R; - -import java.util.List; - -public class ImageSelectionAdapter extends RecyclerView.Adapter { - - private final Context context; - private final List images; - private final OnImageClickListener listener; - - public interface OnImageClickListener { - void onImageClick(Bitmap bitmap); - } - - public ImageSelectionAdapter(Context context, List images, OnImageClickListener listener) { - this.context = context; - this.images = images; - this.listener = listener; - } - - @NonNull - @Override - public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View view = LayoutInflater.from(context).inflate(R.layout.thumbnail_item, parent, false); - return new ViewHolder(view); - } - - @Override - public void onBindViewHolder(@NonNull ViewHolder holder, int position) { - Bitmap image = images.get(position); - holder.imageView.setImageBitmap(image); - holder.imageView.setOnClickListener(v -> listener.onImageClick(image)); - } - - @Override - public int getItemCount() { - return images.size(); - } - - public static class ViewHolder extends RecyclerView.ViewHolder { - ImageView imageView; - - public ViewHolder(@NonNull View itemView) { - super(itemView); - imageView = itemView.findViewById(R.id.imageView); - } - } -} diff --git a/app/src/main/java/com/example/metasearch/ui/adapter/PersonAdapter.java b/app/src/main/java/com/example/metasearch/ui/adapter/PersonAdapter.java deleted file mode 100644 index bd98db4d..00000000 --- a/app/src/main/java/com/example/metasearch/ui/adapter/PersonAdapter.java +++ /dev/null @@ -1,209 +0,0 @@ -package com.example.metasearch.ui.adapter; - -import android.content.Context; -import android.content.Intent; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; -import androidx.annotation.NonNull; -import androidx.appcompat.app.AlertDialog; -import androidx.recyclerview.widget.RecyclerView; -import com.example.metasearch.R; -import com.example.metasearch.utils.DatabaseUtils; -import com.example.metasearch.network.interfaces.WebServerDeleteEntityCallbacks; -import com.example.metasearch.manager.AIRequestManager; -import com.example.metasearch.manager.WebRequestManager; -import com.example.metasearch.data.model.Person; -import com.example.metasearch.ui.activity.PersonPhotosActivity; -import com.example.metasearch.ui.viewmodel.PersonViewModel; -import java.util.List; -import de.hdodenhof.circleimageview.CircleImageView; -import io.github.muddz.styleabletoast.StyleableToast; - -public class PersonAdapter extends RecyclerView.Adapter implements WebServerDeleteEntityCallbacks { - - private static final int VIEW_TYPE_HOME = 0; - private static final int VIEW_TYPE_PERSON = 1; - - private List people; - private final Context context; - private final PersonViewModel personViewModel; - private final boolean isHomeScreen; - - private final WebRequestManager webRequestManager; - private final AIRequestManager aiRequestManager; - - public PersonAdapter(Context context, PersonViewModel personViewModel, boolean isHomeScreen) { - this.context = context; - this.personViewModel = personViewModel; - this.isHomeScreen = isHomeScreen; - this.webRequestManager = WebRequestManager.getWebImageUploader(); - this.aiRequestManager = AIRequestManager.getAiImageUploader(context); - } - - public void setPeople(List people) { - this.people = people; - notifyDataSetChanged(); - } - @Override - public int getItemViewType(int position) { - return isHomeScreen ? VIEW_TYPE_HOME : VIEW_TYPE_PERSON; - } - - @NonNull - @Override - public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - LayoutInflater inflater = LayoutInflater.from(parent.getContext()); - if (viewType == VIEW_TYPE_HOME) { - View view = inflater.inflate(R.layout.person_item_home, parent, false); - return new HomeViewHolder(view); - } else { - View view = inflater.inflate(R.layout.person_item, parent, false); - return new PersonViewHolder(view); - } - } - - @Override - public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { - Person person = people.get(position); - if (person != null) { - if (holder instanceof HomeViewHolder) { - ((HomeViewHolder) holder).bind(person); - } else { - ((PersonViewHolder) holder).bind(person); - } - } - } - - private void showDeleteConfirmationDialog(Person person) { - new AlertDialog.Builder(context, R.style.CustomAlertDialogTheme) - .setTitle("인물 등록 취소") - .setMessage("'" + person.getInputName() + "'님을 '내가 아는 사람들'에서 삭제하시겠습니까?") - .setPositiveButton("삭제", (dialog, which) -> { - personViewModel.deletePerson(person); - webRequestManager.deleteEntity(DatabaseUtils.getPersistentDeviceDatabaseName(context), person.getInputName(), this); - aiRequestManager.deletePerson(DatabaseUtils.getPersistentDeviceDatabaseName(context), person.getImageName()); - }) - .setNegativeButton("취소", null) - .show(); - } - - @Override - public int getItemCount() { - return people == null ? 0 : people.size(); - } - - @Override - public void onDeleteEntitySuccess(String message) { - - StyleableToast.makeText(context, "인물 삭제 완료", R.style.customToast).show(); - - } - - @Override - public void onDeleteEntityFailure(String message) { - StyleableToast.makeText(context, "인물 삭제 실패: " + message, R.style.customToast).show(); - } - - public class PersonViewHolder extends RecyclerView.ViewHolder { - - private final CircleImageView imageView; - private final TextView nameView; - private final ImageView deleteIcon; - - public PersonViewHolder(View itemView) { - super(itemView); - imageView = itemView.findViewById(R.id.face); - nameView = itemView.findViewById(R.id.name); - deleteIcon = itemView.findViewById(R.id.delete_icon); - - deleteIcon.setOnClickListener(v -> { - int position = getAdapterPosition(); - if (position != RecyclerView.NO_POSITION) { - Person person = people.get(position); - showDeleteConfirmationDialog(person); - } - }); - - itemView.setOnClickListener(v -> { - int position = getAdapterPosition(); - if (position != RecyclerView.NO_POSITION) { - Person person = people.get(position); - Intent intent = new Intent(context, PersonPhotosActivity.class); - intent.putExtra("person_id", person.getId()); - context.startActivity(intent); - } - }); - } - - public void bind(Person person) { - nameView.setText(person.getInputName()); -// if (person.getImage() != null) { -// Bitmap imageBitmap = BitmapFactory.decodeByteArray(person.getImage(), 0, person.getImage().length); -// imageView.setImageBitmap(imageBitmap); -// } else { -// imageView.setImageResource(R.drawable.ic_launcher_foreground); -// } - byte[] displayImageData = (person.getThumbnailImage() != null && person.getThumbnailImage().length > 0) ? person.getThumbnailImage() : person.getImage(); - if (displayImageData != null) { - Bitmap imageBitmap = BitmapFactory.decodeByteArray(displayImageData, 0, displayImageData.length); - imageView.setImageBitmap(imageBitmap); - } else { - imageView.setImageResource(R.drawable.ic_launcher_foreground); - } - } - } - - public class HomeViewHolder extends RecyclerView.ViewHolder { - - private final CircleImageView imageView; - private final TextView nameView; -// private final ImageView deleteIcon; - - public HomeViewHolder(View itemView) { - super(itemView); - imageView = itemView.findViewById(R.id.face); - nameView = itemView.findViewById(R.id.name); -// deleteIcon = itemView.findViewById(R.id.delete_icon); -// -// deleteIcon.setOnClickListener(v -> { -// int position = getAdapterPosition(); -// if (position != RecyclerView.NO_POSITION) { -// Person person = people.get(position); -// showDeleteConfirmationDialog(person); -// } -// }); - - itemView.setOnClickListener(v -> { - int position = getAdapterPosition(); - if (position != RecyclerView.NO_POSITION) { - Person person = people.get(position); - Intent intent = new Intent(context, PersonPhotosActivity.class); - intent.putExtra("person_id", person.getId()); - context.startActivity(intent); - } - }); - } - - public void bind(Person person) { - nameView.setText(person.getInputName()); -// if (person.getImage() != null) { -// Bitmap imageBitmap = BitmapFactory.decodeByteArray(person.getImage(), 0, person.getImage().length); -// imageView.setImageBitmap(imageBitmap); -// } else { -// imageView.setImageResource(R.drawable.ic_launcher_foreground); -// } - byte[] displayImageData = (person.getThumbnailImage() != null && person.getThumbnailImage().length > 0) ? person.getThumbnailImage() : person.getImage(); - if (displayImageData != null) { - Bitmap imageBitmap = BitmapFactory.decodeByteArray(displayImageData, 0, displayImageData.length); - imageView.setImageBitmap(imageBitmap); - } else { - imageView.setImageResource(R.drawable.ic_launcher_foreground); - } - } - } -} diff --git a/app/src/main/java/com/example/metasearch/ui/custom/CustomImageView.java b/app/src/main/java/com/example/metasearch/ui/custom/CustomImageView.java deleted file mode 100644 index 03556994..00000000 --- a/app/src/main/java/com/example/metasearch/ui/custom/CustomImageView.java +++ /dev/null @@ -1,121 +0,0 @@ -package com.example.metasearch.ui.custom; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.net.Uri; -import android.util.AttributeSet; -import android.util.Log; -import android.view.MotionEvent; -import android.widget.FrameLayout; - -import androidx.appcompat.widget.AppCompatImageView; -import androidx.core.content.ContextCompat; - -import java.io.FileNotFoundException; -import java.util.ArrayList; -import java.util.List; - -import com.example.metasearch.R; -import com.example.metasearch.data.model.Circle; - -import io.github.muddz.styleabletoast.StyleableToast; - -public class CustomImageView extends AppCompatImageView { - private final List circles = new ArrayList<>(); // 원들을 저장할 리스트 - private Paint paint; - private float startX, startY, currentX, currentY; - private boolean isDrawing = false; - - public CustomImageView(Context context, AttributeSet attrs) { - super(context, attrs); - init(context); - } - private void init(Context context) { - int color = ContextCompat.getColor(context, R.color.white); - paint = new Paint(); - paint.setAntiAlias(true); - paint.setColor(color); - paint.setStyle(Paint.Style.STROKE); - paint.setStrokeWidth(6); - } - public void setCircleColor(int color) { - paint.setColor(color); - invalidate(); // 변경된 색으로 다시 그리기 - } - public List getCircles() { - return new ArrayList<>(circles); // 원 정보를 포함하는 리스트 반환 - } - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - for (Circle circle : circles) { - float drawCenterX = circle.getCenterX() * getWidth(); - float drawCenterY = circle.getCenterY() * getHeight(); - float drawRadius = circle.getRadius() * Math.max(getWidth(), getHeight()); - canvas.drawCircle(drawCenterX, drawCenterY, drawRadius, paint); - } - if (isDrawing) { - float radius = (float) Math.hypot(currentX - startX, currentY - startY) / 2; - canvas.drawCircle((startX + currentX) / 2, (startY + currentY) / 2, radius, paint); - } - } - @Override - public boolean onTouchEvent(MotionEvent event) { - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - if (circles.size() >= 5) { - StyleableToast.makeText(getContext(), "최대 5개의 원만 그릴 수 있습니다.", R.style.customToast).show(); - return false; // 더 이상 그리지 않음 - } - startX = event.getX(); - startY = event.getY(); - isDrawing = true; - break; - case MotionEvent.ACTION_MOVE: - currentX = event.getX(); - currentY = event.getY(); - invalidate(); // 캔버스를 다시 그리도록 요청 - break; - case MotionEvent.ACTION_UP: - float radius = (float) Math.hypot(currentX - startX, currentY - startY) / 2; - float normalizedCenterX = (startX + currentX) / 2 / getWidth(); - float normalizedCenterY = (startY + currentY) / 2 / getHeight(); - float normalizedRadius = radius / Math.max(getWidth(), getHeight()); - - Log.d("CIRCLE_RADIUS", String.valueOf(radius)); - Log.d("CIRCLE_X", String.valueOf(currentX)); - Log.d("CIRCLE_y", String.valueOf(currentY)); - - Log.d("NORMALIZED_RADIUS", String.valueOf(normalizedRadius)); - Log.d("NORMALIZED_X", String.valueOf(normalizedCenterX)); - Log.d("NORMALIZED_Y", String.valueOf(normalizedCenterY)); - - circles.add(new Circle(normalizedCenterX, normalizedCenterY, normalizedRadius)); - isDrawing = false; - invalidate(); - break; - } - return true; // 이벤트를 처리했음을 시스템에 알림 - } - public void setImageUri(Uri imageUri) { - // 이미지 설정 메서드 추가 - try { - Bitmap bitmap = BitmapFactory.decodeStream(getContext().getContentResolver().openInputStream(imageUri)); - setImageBitmap(bitmap); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } - } - public void clearCircles() { - circles.clear(); - if (getParent() instanceof FrameLayout) { - FrameLayout parent = (FrameLayout) getParent(); - parent.removeAllViews(); - parent.addView(this); // CustomImageView를 다시 추가 - } - invalidate(); // 화면을 다시 그림 - } -} diff --git a/app/src/main/java/com/example/metasearch/ui/fragment/GraphFragment.java b/app/src/main/java/com/example/metasearch/ui/fragment/GraphFragment.java deleted file mode 100644 index 1573e53f..00000000 --- a/app/src/main/java/com/example/metasearch/ui/fragment/GraphFragment.java +++ /dev/null @@ -1,175 +0,0 @@ -package com.example.metasearch.ui.fragment; - -import static com.example.metasearch.utils.GalleryImageManager.findMatchedUri; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.net.http.SslError; -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.webkit.JavascriptInterface; -import android.webkit.SslErrorHandler; -import android.webkit.WebView; -import android.webkit.WebViewClient; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.fragment.app.Fragment; -import androidx.lifecycle.ViewModelProvider; - -import com.example.metasearch.databinding.FragmentGraphBinding; -import com.example.metasearch.utils.DatabaseUtils; -import com.example.metasearch.ui.activity.GraphDisplayActivity; -import com.example.metasearch.ui.viewmodel.GraphViewModel; - -import java.util.ArrayList; -import java.util.List; - -public class GraphFragment extends Fragment { - - private FragmentGraphBinding binding; - - private List imageViews = new ArrayList<>(); // ImageView 객체 저장 - private static final int MAX_IMAGES = 10; // 최대 이미지 수 - - public View onCreateView(@NonNull LayoutInflater inflater, - ViewGroup container, Bundle savedInstanceState) { - GraphViewModel graphViewModel = - new ViewModelProvider(this).get(GraphViewModel.class); - - binding = FragmentGraphBinding.inflate(inflater, container, false); - View root = binding.getRoot(); - - binding.webView.setWebViewClient(new WebViewClient() { - @Override - public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { - // SSL 에러가 발생해도 계속 진행 - handler.proceed(); - } - }); - - binding.webView.getSettings().setJavaScriptEnabled(true); //자바스크립트 실행을 허용. 이거 꼭 해줘야 화면 나타남 - binding.webView.getSettings().setLoadWithOverviewMode(true); // WebView 화면크기에 맞추도록 설정 - setUseWideViewPort 와 같이 써야함 - binding.webView.getSettings().setUseWideViewPort(true); // wide viewport 설정 - setLoadWithOverviewMode 와 같이 써야함 - - // JavascriptInterface 추가 - binding.webView.addJavascriptInterface(new WebAppInterface(requireContext()), "Android"); - - // 수정 후 코드 - binding.webView.loadUrl("http://113.198.85.6/graph/" + DatabaseUtils.getPersistentDeviceDatabaseName(getContext())); - Log.d("WEBVIEW_URL", "http://113.198.85.6/graph/" + DatabaseUtils.getPersistentDeviceDatabaseName(getContext())); - - return root; - } - - private Handler handler = new Handler(Looper.getMainLooper()); - public class WebAppInterface { - Context mContext; - - WebAppInterface(Context c) { - mContext = c; - } - - // 자바스크립트에서 호출할 수 있는 메서드 - @JavascriptInterface - public void receivePhotoName(final String photoName) { - // 새 스레드에서 작업 실행 - new Thread(new Runnable() { - @Override - public void run() { - // 백그라운드 스레드에서 갤러리 이미지 이름들을 가져오기 - final Uri matchedUri = findMatchedUri(photoName, requireContext()); - // 메인 스레드에서 UI 업데이트 - handler.post(new Runnable() { - @Override - public void run() { - if (matchedUri != null) { - addImageToGallery(matchedUri); - } else { - @SuppressLint("ShowToast") Toast toast = Toast.makeText(mContext, "사진을 찾지 못했습니다", Toast.LENGTH_SHORT); - toast.show(); - - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - toast.cancel(); - } - }, 500); // 1000ms = 1초 후에 실행 - } - } - }); - } - }).start(); - } - } - - private List existingUris = new ArrayList<>(); // 이미 추가된 이미지 URI 목록 - - private void addImageToGallery(Uri uri) { - if (existingUris.contains(uri)) { - Toast.makeText(getContext(), "이미 추가된 사진입니다.", Toast.LENGTH_SHORT).show(); - return; // 이미 목록에 있는 URI이면 추가하지 않고 종료 - } - - ImageView imageView = new ImageView(getContext()); - int sizeInPixels = dpToPx(300); // 이미지 크기를 픽셀 단위로 설정 - imageView.setLayoutParams(new LinearLayout.LayoutParams(sizeInPixels, sizeInPixels)); - imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); - imageView.setImageURI(uri); - - // 최대 이미지 수를 초과하면 가장 오래된 이미지 제거 - if (imageViews.size() >= MAX_IMAGES) { - ImageView oldestView = imageViews.remove(imageViews.size() - 1); - binding.imageContainer.removeView(oldestView); - existingUris.remove(existingUris.size() - 1); // 가장 오래된 URI도 제거 - } - - // 새 이미지 및 URI 추가 - imageViews.add(0, imageView); - binding.imageContainer.addView(imageView, 0); - existingUris.add(0, uri); // 새 URI를 리스트의 맨 앞에 추가 - - imageView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Intent intent = new Intent(requireContext(), GraphDisplayActivity.class); - intent.putExtra("imageUri", uri.toString()); - startActivity(intent); - } - }); - - imageView.setOnLongClickListener(v -> { - shareImage(uri); - return true; - }); - } - - - // dp를 픽셀로 변환하는 유틸리티 메소드 - private int dpToPx(int dp) { - return (int) (dp * getResources().getDisplayMetrics().density); - } - - // 이미지 공유를 위한 메소드 - private void shareImage(Uri imageUri) { - Intent shareIntent = new Intent(Intent.ACTION_SEND); - shareIntent.putExtra(Intent.EXTRA_STREAM, imageUri); - shareIntent.setType("image/*"); - startActivity(Intent.createChooser(shareIntent, "사진 공유")); - } - - @Override - public void onDestroyView() { - super.onDestroyView(); - binding = null; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/metasearch/ui/fragment/HomeFragment.java b/app/src/main/java/com/example/metasearch/ui/fragment/HomeFragment.java deleted file mode 100644 index 6466441d..00000000 --- a/app/src/main/java/com/example/metasearch/ui/fragment/HomeFragment.java +++ /dev/null @@ -1,190 +0,0 @@ -package com.example.metasearch.ui.fragment; - -import static com.example.metasearch.utils.GalleryImageManager.getAllGalleryImagesUri; - -import android.annotation.SuppressLint; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import androidx.lifecycle.ViewModelProvider; -import androidx.recyclerview.widget.GridLayoutManager; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import androidx.work.Constraints; -import androidx.work.NetworkType; -import androidx.work.OneTimeWorkRequest; -import androidx.work.WorkManager; -import androidx.work.WorkRequest; - -import com.example.metasearch.R; -import com.example.metasearch.data.dao.DatabaseHelper; -import com.example.metasearch.databinding.FragmentHomeBinding; -import com.example.metasearch.network.interfaces.ImageAnalysisCompleteListener; -import com.example.metasearch.network.interfaces.Update; -import com.example.metasearch.network.interfaces.WebServerPersonFrequencyUploadCallbacks; -import com.example.metasearch.manager.ImageAnalysisWorker; -import com.example.metasearch.manager.ImageServiceRequestManager; -import com.example.metasearch.manager.WebRequestManager; -import com.example.metasearch.data.model.Person; -import com.example.metasearch.network.response.PersonFrequencyResponse; -import com.example.metasearch.ui.activity.ImageDisplayActivity; -import com.example.metasearch.ui.activity.MainActivity; -import com.example.metasearch.ui.adapter.ImageAdapter; -import com.example.metasearch.ui.adapter.PersonAdapter; -import com.example.metasearch.ui.viewmodel.PersonViewModel; - -import java.util.List; - -import io.github.muddz.styleabletoast.StyleableToast; - -public class HomeFragment extends Fragment - implements ImageAdapter.OnImageClickListener, Update, ImageAnalysisCompleteListener, - WebServerPersonFrequencyUploadCallbacks { - - private FragmentHomeBinding binding; - private DatabaseHelper databaseHelper; - private ImageServiceRequestManager imageServiceRequestManager; - private WebRequestManager webRequestManager; - - private boolean isRecyclerViewVisible = true; - private PersonAdapter personAdapter; - private PersonViewModel personViewModel; - @SuppressLint("ClickableViewAccessibility") - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - binding = FragmentHomeBinding.inflate(inflater, container, false); - return binding.getRoot(); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - init(); - setupListeners(); - setupRecyclerViews(); - setupRecyclerViewToggle(); - setupViewModel(); - loadAllGalleryImages(); - } - - private void init() { - databaseHelper = DatabaseHelper.getInstance(getContext()); - imageServiceRequestManager = ImageServiceRequestManager.getInstance(getContext(), databaseHelper); - imageServiceRequestManager.setImageAnalysisCompleteListener(this); - webRequestManager = WebRequestManager.getWebImageUploader(); - } - - private void setupRecyclerViews() { - personAdapter = new PersonAdapter(getContext(), personViewModel, true); - LinearLayoutManager layoutManager = new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false); - binding.personRecyclerViewHorizon.setLayoutManager(layoutManager); - binding.personRecyclerViewHorizon.setAdapter(personAdapter); - - binding.personRecyclerViewHorizon.setVisibility(View.GONE); // 초기 상태를 GONE으로 설정 - } - - private void setupRecyclerViewToggle() { - binding.btn.setOnClickListener(v -> { - if (isRecyclerViewVisible) { - binding.personRecyclerViewHorizon.setVisibility(View.GONE); - binding.btn.setImageResource(R.drawable.icon_down); - } else { - binding.personRecyclerViewHorizon.setVisibility(View.VISIBLE); - binding.btn.setImageResource(R.drawable.icon_up); - -// personViewModel.fetchPeopleFromLocalDatabase(); -// personViewModel.filterHomeScreen(); - } - isRecyclerViewVisible = !isRecyclerViewVisible; - }); - } - private void setupViewModel() { - personViewModel = new ViewModelProvider(this).get(PersonViewModel.class); - personViewModel.getHomeDisplayPeople().observe(getViewLifecycleOwner(), this::updateRecyclerView); - } - - private void updateRecyclerView(List people) { - personAdapter.setPeople(people); - } - public void startImageAnalysis() { - Constraints constraints = new Constraints.Builder() - .setRequiredNetworkType(NetworkType.CONNECTED) - .build(); - - WorkRequest uploadWorkRequest = new OneTimeWorkRequest.Builder(ImageAnalysisWorker.class) - .setConstraints(constraints) - .build(); - - WorkManager.getInstance(requireContext()).enqueue(uploadWorkRequest); - } - - private void setupListeners() { - binding.galleryRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { - @Override - public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { - super.onScrolled(recyclerView, dx, dy); - MainActivity activity = (MainActivity) getActivity(); - if (activity != null) { - if (dy > 0) { - activity.hideBottomNavigationView(); - } else if (dy < 0) { - activity.showBottomNavigationView(); - } - } - } - }); - } - - @Override - public void onPersonFrequencyUploadFailure(String message) { -// Log.e("HomeFragment", "Data fetch failed: " + message); - StyleableToast.makeText(getContext(), "데이터 불러오기 실패: " + message, R.style.customToast).show(); - } - public void loadAllGalleryImages() { - ImageAdapter adapter = new ImageAdapter(getAllGalleryImagesUri(requireContext()), requireContext(), this); - GridLayoutManager layoutManager = new GridLayoutManager(requireContext(), 5); - binding.galleryRecyclerView.setAdapter(adapter); - binding.galleryRecyclerView.setLayoutManager(layoutManager); - } - @Override - public void onPersonFrequencyUploadSuccess(PersonFrequencyResponse response) { - - } - @Override - public void onImageClick(Uri uri) { - Intent intent = new Intent(requireContext(), ImageDisplayActivity.class); - intent.putExtra("imageUri", uri.toString()); - startActivity(intent); - } - - @Override - public void onDestroyView() { - super.onDestroyView(); - binding = null; - } - - @Override - public void onResume() { - super.onResume(); - personViewModel.fetchPeopleFromLocalDatabase(); - personViewModel.filterHomeScreen(); - loadAllGalleryImages(); - } - - @Override - public void performDataUpdate() { - startImageAnalysis(); - } - - @Override - public void onImageAnalysisComplete() { - - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/metasearch/ui/fragment/LikeFragment.java b/app/src/main/java/com/example/metasearch/ui/fragment/LikeFragment.java deleted file mode 100644 index 9e49c2c1..00000000 --- a/app/src/main/java/com/example/metasearch/ui/fragment/LikeFragment.java +++ /dev/null @@ -1,28 +0,0 @@ -//package com.example.metasearch.ui.fragment; -// -//import android.os.Bundle; -//import android.view.LayoutInflater; -//import android.view.View; -//import android.view.ViewGroup; -// -//import androidx.annotation.NonNull; -//import androidx.fragment.app.Fragment; -// -//import com.example.metasearch.databinding.FragmentLikeBinding; -// -//public class LikeFragment extends Fragment { -// private FragmentLikeBinding binding; -// public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { -// binding = FragmentLikeBinding.inflate(inflater, container, false); -// View root = binding.getRoot(); -// -// -// -// return root; -// } -// @Override -// public void onDestroyView() { -// super.onDestroyView(); -// binding = null; -// } -//} diff --git a/app/src/main/java/com/example/metasearch/ui/fragment/PersonFragment.java b/app/src/main/java/com/example/metasearch/ui/fragment/PersonFragment.java deleted file mode 100644 index cc05af23..00000000 --- a/app/src/main/java/com/example/metasearch/ui/fragment/PersonFragment.java +++ /dev/null @@ -1,161 +0,0 @@ -package com.example.metasearch.ui.fragment; - -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.widget.SearchView; -import androidx.fragment.app.Fragment; -import androidx.lifecycle.ViewModelProvider; -import androidx.recyclerview.widget.GridLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import com.example.metasearch.R; -import com.example.metasearch.databinding.FragmentPersonBinding; -import com.example.metasearch.network.interfaces.ImageAnalysisCompleteListener; -import com.example.metasearch.data.model.Person; -import com.example.metasearch.ui.activity.MainActivity; -import com.example.metasearch.ui.adapter.PersonAdapter; -import com.example.metasearch.ui.viewmodel.PersonViewModel; - -import java.util.List; - -public class PersonFragment extends Fragment implements ImageAnalysisCompleteListener { - - private FragmentPersonBinding binding; - private PersonViewModel viewModel; - private PersonAdapter personAdapter; - - private static final String SPINNER_SELECTED_POSITION = "spinner_selected_position"; - private int spinnerSelectedPosition = 0; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - binding = FragmentPersonBinding.inflate(inflater, container, false); - return binding.getRoot(); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - if (savedInstanceState != null) { - spinnerSelectedPosition = savedInstanceState.getInt(SPINNER_SELECTED_POSITION, 0); - } - setupViewModel(); - setupRecyclerView(); - setupListeners(); - setupSearchView(); - setupSpinner(); - } - - @Override - public void onSaveInstanceState(@NonNull Bundle outState) { - super.onSaveInstanceState(outState); - outState.putInt(SPINNER_SELECTED_POSITION, binding.spinnerFilter.getSelectedItemPosition()); - } - - - @Override - public void onResume() { - super.onResume(); - - viewModel.fetchPeopleFromLocalDatabase(); - binding.spinnerFilter.setSelection(spinnerSelectedPosition); - } - - private void setupRecyclerView() { - personAdapter = new PersonAdapter(requireContext(), viewModel, false); - GridLayoutManager gridLayoutManager = new GridLayoutManager(requireContext(), 3); - binding.personRecyclerView.setLayoutManager(gridLayoutManager); - binding.personRecyclerView.setAdapter(personAdapter); - } - - private void setupViewModel() { - viewModel = new ViewModelProvider(this).get(PersonViewModel.class); - viewModel.getPeople().observe(getViewLifecycleOwner(), this::updateRecyclerView); - } - - private void setupSearchView() { - binding.searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { - @Override - public boolean onQueryTextSubmit(String query) { - viewModel.filterPeople(query); - return false; - } - - @Override - public boolean onQueryTextChange(String newText) { - viewModel.filterPeople(newText); - return false; - } - }); - } - private void setupListeners() { - binding.personRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { - @Override - public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { - super.onScrolled(recyclerView, dx, dy); - MainActivity activity = (MainActivity) getActivity(); - if (activity != null) { - if (dy > 0) { - activity.hideBottomNavigationView(); - } else if (dy < 0) { - activity.showBottomNavigationView(); - } - } - } - }); - } - private void setupSpinner() { - ArrayAdapter adapter = ArrayAdapter.createFromResource(requireContext(), - R.array.filter_options, android.R.layout.simple_spinner_item); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - binding.spinnerFilter.setAdapter(adapter); - - binding.spinnerFilter.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - if (spinnerSelectedPosition != position) { - spinnerSelectedPosition = position; - applySpinnerSelection(position); - } - } - - @Override - public void onNothingSelected(AdapterView parent) { - // 아무것도 선택되지 않았을 때의 동작 - } - }); - - // 스피너 상태 복원 - binding.spinnerFilter.setSelection(spinnerSelectedPosition); - } - - private void applySpinnerSelection(int position) { - switch (position) { - case 0: - viewModel.sortAlphabetical(); - break; - case 1: - viewModel.sortByPhotoCount(); - break; - case 2: - viewModel.filterHomeScreen(); - break; - } - } - private void updateRecyclerView(List people) { - personAdapter.setPeople(people); - } - - @Override - public void onImageAnalysisComplete() { - // 이미지 분석 작업이 완료되면 호출되는 콜백 - viewModel.fetchPeopleFromLocalDatabase(); - } -} diff --git a/app/src/main/java/com/example/metasearch/ui/fragment/SearchFragment.java b/app/src/main/java/com/example/metasearch/ui/fragment/SearchFragment.java deleted file mode 100644 index eaf6d52e..00000000 --- a/app/src/main/java/com/example/metasearch/ui/fragment/SearchFragment.java +++ /dev/null @@ -1,308 +0,0 @@ -package com.example.metasearch.ui.fragment; - -import static com.example.metasearch.utils.GalleryImageManager.findMatchedUris; - -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.text.Editable; -import android.text.TextWatcher; -import android.util.Log; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; - -import androidx.annotation.NonNull; -import androidx.fragment.app.Fragment; -import androidx.lifecycle.ViewModelProvider; -import androidx.recyclerview.widget.GridLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import com.example.metasearch.BuildConfig; -import com.example.metasearch.R; -import com.example.metasearch.databinding.FragmentSearchBinding; -import com.example.metasearch.utils.DatabaseUtils; -import com.example.metasearch.utils.HttpHelper; -import com.example.metasearch.network.interfaces.Update; -import com.example.metasearch.network.interfaces.WebServerQueryCallbacks; -import com.example.metasearch.utils.Neo4jDatabaseManager; -import com.example.metasearch.manager.WebRequestManager; -import com.example.metasearch.data.model.Choice; -import com.example.metasearch.data.model.Message; -import com.example.metasearch.network.request.OpenAIRequest; -import com.example.metasearch.network.response.OpenAIResponse; -import com.example.metasearch.network.response.PhotoNameResponse; -import com.example.metasearch.network.api.ApiService; -import com.example.metasearch.ui.activity.ImageDisplayActivity; -import com.example.metasearch.ui.activity.MainActivity; -import com.example.metasearch.ui.adapter.CustomArrayAdapter; -import com.example.metasearch.ui.adapter.ImageAdapter; -import com.example.metasearch.ui.viewmodel.ImageViewModel; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import io.github.muddz.styleabletoast.StyleableToast; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -public class SearchFragment extends Fragment - implements ImageAdapter.OnImageClickListener, Update { - private WebRequestManager webRequestManager; - private static final String OPENAI_URL = "https://api.openai.com/"; - private ImageViewModel imageViewModel; - private FragmentSearchBinding binding; - private String userInputText = ""; // 사용자 입력을 추적하는 변수 - private String neo4jQuery = ""; // Neo4j 서버에 보낼 쿼리문 - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - imageViewModel = new ViewModelProvider(this).get(ImageViewModel.class); - binding = FragmentSearchBinding.inflate(inflater, container, false); - View root = binding.getRoot(); - - init(); - setupRecyclerView(); - setupListeners(); - setupAutoCompleteTextView(); - - return root; - } - private void init() { - imageViewModel.getImageUris().observe(getViewLifecycleOwner(), this::updateRecyclerView); - webRequestManager = WebRequestManager.getWebImageUploader(); // 인스턴스 생성 - } - private void setupListeners() { - binding.searchButton.setOnClickListener(v -> retrieve()); - // 리사이클러뷰 스크롤에 따라 하단의 네비바 높이 조절 - binding.recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { - @Override - public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { - super.onScrolled(recyclerView, dx, dy); - MainActivity activity = (MainActivity) getActivity(); - if (activity != null) { - if (dy > 0) { - // 스크롤 내릴 때, 네비게이션 바 숨기기 - activity.hideBottomNavigationView(); - } else if (dy < 0) { - // 스크롤 올릴 때, 네비게이션 바 보이기 - activity.showBottomNavigationView(); - } - } - } - }); - - binding.searchText.setOnKeyListener(new View.OnKeyListener() { - @Override - public boolean onKey(View v, int keyCode, KeyEvent event) { - // 엔터 키가 눌렸는지 확인 - if (keyCode == KeyEvent.KEYCODE_ENTER && event.getAction() == KeyEvent.ACTION_DOWN) { - // 검색 실행 코드 - retrieve(); - return true; // 이벤트 처리 완료 - } - return false; // 다른 키 이벤트는 기본 동작 수행 - } - }); - } - // OpenAI API 사용해서 사진 검색 - public void photoSearch() { - // 사용자가 검색한 문장 - String userInput = binding.searchText.getText().toString(); - // 사용자가 아무것도 입력하지 않고 검색 버튼 클릭 시, api 호출하지 않도록 리턴 - if (userInput.length() == 0) { - // 사용자가 아무것도 입력하지 않았을 때 UI 업데이트와 로직을 종료 - getActivity().runOnUiThread(() -> { - StyleableToast.makeText(getContext(), "검색어를 입력해주세요.", R.style.customToast).show(); - binding.searchButton.setEnabled(true); - binding.spinKit.setVisibility(View.GONE); - updateUIWithMatchedUris(new ArrayList<>()); - }); - - return; // 메서드를 여기서 종료 - } - // 사용자가 입력한 문장(찾고 싶은 사진) + gpt가 분석할 수 있도록 지시할 문장 - userInput = getString(R.string.user_input_kor) + userInput; - if (userInput.length() == 0) return; - ApiService service = HttpHelper.getInstance(OPENAI_URL).create(ApiService.class); - - List messages = new ArrayList<>(); - messages.add(new Message("user", userInput)); - OpenAIRequest request = new OpenAIRequest("gpt-3.5-turbo", messages); - String apiKey = "Bearer " + BuildConfig.OPENAI_API_KEY; - - service.createChatCompletion(apiKey, request).enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - // 응답 처리 로직 - // 성공적으로 응답을 받았을 경우 - if (response.isSuccessful()) { - StringBuilder msg = new StringBuilder(); - OpenAIResponse openAIResponse = response.body(); - assert openAIResponse != null; - - for (Choice choice : openAIResponse.getChoices()) { - String text = choice.getMessage().getContent().trim(); - // 여기에서 응답 처리, 예: TextView에 출력 - msg.append(text).append("\n"); - System.out.println("response : " + text); // test - - // 응답 값이 "0"일 때 예외 처리 - if (text.equals("0")) { - Log.e("OpenAI Response", "No results found."); - getActivity().runOnUiThread(() -> { - StyleableToast.makeText(getContext(), "검색된 결과가 없습니다.", R.style.customToast).show(); - binding.searchButton.setEnabled(true); - binding.spinKit.setVisibility(View.GONE); - updateUIWithMatchedUris(new ArrayList<>()); - }); - return; - } - - // 콤마로 구분된 응답을 분리하여 entities 리스트에 추가 - String[] parts = text.split(","); - List entities = new ArrayList<>(); - for (String part : parts) { - entities.add(part.trim()); // 공백 제거 후 추가 - } - // test - System.out.println(entities); - - // 사이퍼 쿼리 생성 - neo4jQuery = Neo4jDatabaseManager.createCypherQueryForEntities(entities); - System.out.println("TEST_QUERY : " + neo4jQuery); - } - // UI 업데이트는 메인 스레드에서 실행 -// binding.textView2.post(() -> binding.textView2.setText(msg)); -// // 생성된 사이퍼쿼리를 텍스트뷰에 출력해서 확인 가능 -// binding.query.post(() -> binding.query.setText(neo4jQuery)); - - // 웹 서버에 쿼리 전송 - webRequestManager.sendQueryToWebServer(DatabaseUtils.getPersistentDeviceDatabaseName(getContext()), neo4jQuery , new WebServerQueryCallbacks() { - @Override - public void onWebServerQuerySuccess(PhotoNameResponse photoNameResponse) { - List matchedUris = findMatchedUris(photoNameResponse.getPhotoName(), requireContext()); - - updateUIWithMatchedUris(matchedUris); - } - - @Override - public void onWebServerQueryFailure() { - updateUIWithMatchedUris(new ArrayList<>()); - } - }); - } else { - Log.e("OpenAI Error", "Error fetching response"); - } - binding.searchButton.setEnabled(true); // 버튼 활성화 - binding.spinKit.setVisibility(View.GONE); // 로딩 아이콘 숨김 - } - @Override - public void onFailure(Call call, Throwable t) { - Log.e("OpenAI Failure", t.getMessage()); - binding.searchButton.setEnabled(true); // 버튼 활성화 - binding.spinKit.setVisibility(View.GONE); // 로딩 아이콘 숨김 - } - }); - } - // 검색어 자동 완성 기능을 가지는 검색 창 - private void setupAutoCompleteTextView() { - // 배열 리소스에서 아이템(추천 단어 리스트) 가져오기 - String[] items = getResources().getStringArray(R.array.autocomplete_items); - // 어댑터에 아이템 설정 - List itemList = new ArrayList<>(Arrays.asList(items)); - CustomArrayAdapter adapter = new CustomArrayAdapter(requireContext(), R.layout.custom_autocomplete_item, itemList); - // 자동 완성 텍스트 뷰에 어댑터 연결 - binding.searchText.setAdapter(adapter); - // 자동 완성 시작 글자 수 설정 - binding.searchText.setThreshold(1); - // 추천 단어가 출력 되는 뷰와 입력 뷰 간의 간격 조정 - binding.searchText.setDropDownVerticalOffset((int) (10 * getResources().getDisplayMetrics().density)); - - // TextWatcher를 추가하여 사용자 입력 추적 - binding.searchText.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) {} - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - // 사용자가 직접 입력할 때만 추적 - if (!binding.searchText.isPerformingCompletion()) { - userInputText = s.toString(); - adapter.setUserInputText(userInputText); // adapter에 사용자 입력 전달 - } - } - - @Override - public void afterTextChanged(Editable s) {} - }); - binding.searchText.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - // 선택된 아이템 - String selectedItem = parent.getItemAtPosition(position).toString(); - - // 마지막 공백이 있었던 위치 - int lastSpacePosition = userInputText.lastIndexOf(' '); - - // 마지막 단어를 대체하거나, 공백 뒤에 선택한 아이템 추가 - String newText; - if (lastSpacePosition != -1) { - newText = userInputText.substring(0, lastSpacePosition + 1) + selectedItem + " "; - } else { - newText = selectedItem + " "; - } - // AutoCompleteTextView에 새로운 텍스트를 설정하고, 커서 위치를 조정 - binding.searchText.setText(newText); - binding.searchText.setSelection(newText.length()); - } - }); - } - private void setupRecyclerView() { - GridLayoutManager layoutManager = new GridLayoutManager(requireContext(), 5); - binding.recyclerView.setLayoutManager(layoutManager); - } - private void updateRecyclerView(List imageUris) { - ImageAdapter adapter = new ImageAdapter(imageUris, requireContext(), this); - binding.recyclerView.setAdapter(adapter); - } - // 새로운 이미지로 리사이클러뷰 업데이트 - private void updateUIWithMatchedUris(List matchedUris) { - if (matchedUris.isEmpty()) { - Log.d("SearchFragment", "No matched photos found, updating with empty list."); - } - getActivity().runOnUiThread(() -> { - if (binding != null && imageViewModel != null) { - imageViewModel.setImageUris(matchedUris); - binding.recyclerView.getAdapter().notifyDataSetChanged(); - } - }); - } - private void retrieve() { - binding.searchButton.setEnabled(false); - binding.spinKit.setVisibility(View.VISIBLE); - // 데이터 베이스 작업은 별도의 스레드에서 실행 - new Thread(this::photoSearch).start(); - } - @Override - public void onDestroyView() { - super.onDestroyView(); - binding = null; - } - @Override - public void onImageClick(Uri uri) { - Intent intent = new Intent(requireContext(), ImageDisplayActivity.class); - intent.putExtra("imageUri", uri.toString()); - startActivity(intent); - } - // 화면 초기화 - @Override - public void performDataUpdate() { - updateUIWithMatchedUris(new ArrayList<>()); // 검색된 사진 제거 - binding.searchText.setText(""); // 검색한 문장 초기화 - } -} diff --git a/app/src/main/java/com/example/metasearch/ui/viewmodel/GraphViewModel.java b/app/src/main/java/com/example/metasearch/ui/viewmodel/GraphViewModel.java deleted file mode 100644 index cb25357a..00000000 --- a/app/src/main/java/com/example/metasearch/ui/viewmodel/GraphViewModel.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.example.metasearch.ui.viewmodel; - -import androidx.lifecycle.LiveData; -import androidx.lifecycle.MutableLiveData; -import androidx.lifecycle.ViewModel; - -public class GraphViewModel extends ViewModel { - - private final MutableLiveData mText; - - public GraphViewModel() { - mText = new MutableLiveData<>(); - mText.setValue("This is graph fragment"); - } - - public LiveData getText() { - return mText; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/metasearch/ui/viewmodel/ImageViewModel.java b/app/src/main/java/com/example/metasearch/ui/viewmodel/ImageViewModel.java deleted file mode 100644 index a65354c6..00000000 --- a/app/src/main/java/com/example/metasearch/ui/viewmodel/ImageViewModel.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.example.metasearch.ui.viewmodel; - -import android.net.Uri; - -import androidx.lifecycle.LiveData; -import androidx.lifecycle.MutableLiveData; -import androidx.lifecycle.ViewModel; - -import java.util.ArrayList; -import java.util.List; - -public class ImageViewModel extends ViewModel { - private MutableLiveData> imageUrisLiveData = new MutableLiveData<>(); - - // 이미지 URI 리스트를 설정하는 메서드 - public void setImageUris(List imageUris) { - if (imageUris == null) { - imageUrisLiveData.setValue(new ArrayList<>()); // null 대신 빈 리스트를 설정 - } else { - imageUrisLiveData.setValue(imageUris); - } - } - - // 이미지 URI 리스트를 반환하는 메서드 - public LiveData> getImageUris() { - return imageUrisLiveData; - } -} - diff --git a/app/src/main/java/com/example/metasearch/ui/viewmodel/PersonViewModel.java b/app/src/main/java/com/example/metasearch/ui/viewmodel/PersonViewModel.java deleted file mode 100644 index a94dd8d0..00000000 --- a/app/src/main/java/com/example/metasearch/ui/viewmodel/PersonViewModel.java +++ /dev/null @@ -1,188 +0,0 @@ -package com.example.metasearch.ui.viewmodel; - -import android.Manifest; -import android.app.Application; -import android.content.pm.PackageManager; -import android.util.Log; - -import androidx.annotation.NonNull; -import androidx.core.content.ContextCompat; -import androidx.lifecycle.AndroidViewModel; -import androidx.lifecycle.LiveData; -import androidx.lifecycle.MutableLiveData; - -import com.example.metasearch.data.dao.DatabaseHelper; -import com.example.metasearch.utils.DatabaseUtils; -import com.example.metasearch.network.interfaces.WebServerPersonFrequencyUploadCallbacks; -import com.example.metasearch.manager.WebRequestManager; -import com.example.metasearch.data.model.Person; -import com.example.metasearch.network.response.PersonFrequencyResponse; - -import java.util.ArrayList; -import java.util.List; - -public class PersonViewModel extends AndroidViewModel implements WebServerPersonFrequencyUploadCallbacks { - - private final MutableLiveData> filteredPeopleLiveData = new MutableLiveData<>(); - private final MutableLiveData> homeDisplayPeopleLiveData = new MutableLiveData<>(); - private List allPeople = new ArrayList<>(); - private final DatabaseHelper databaseHelper; - private final WebRequestManager webRequestManager; - - private String currentFilterQuery = ""; - private int currentSortOption = 0; // 0: Alphabetical, 1: By Photo Count, 2: Home Display - - public PersonViewModel(@NonNull Application application) { - super(application); - databaseHelper = DatabaseHelper.getInstance(application.getApplicationContext()); - webRequestManager = WebRequestManager.getWebImageUploader(); - fetchPeopleFromLocalDatabase(); - } - - public LiveData> getPeople() { - return filteredPeopleLiveData; - } - - public LiveData> getHomeDisplayPeople() { - return homeDisplayPeopleLiveData; - } - - public void fetchPeopleFromLocalDatabase() { - if (ContextCompat.checkSelfPermission(getApplication(), Manifest.permission.READ_CALL_LOG) == PackageManager.PERMISSION_GRANTED) { - allPeople = databaseHelper.getUniquePersons(); - if (allPeople.isEmpty()) { - Log.e("PersonViewModel", "No persons found in the local database."); - // 빈 목록 처리를 위한 추가 작업 - filteredPeopleLiveData.setValue(new ArrayList<>()); - homeDisplayPeopleLiveData.setValue(new ArrayList<>()); - } else { - fetchPersonFrequencies(); - } - } else { - Log.e("PersonViewModel", "READ_CALL_LOG permission is not granted."); - // 권한이 없을 때 빈 목록 설정 - filteredPeopleLiveData.setValue(new ArrayList<>()); - homeDisplayPeopleLiveData.setValue(new ArrayList<>()); - } - } - private void normalizeScores(List people) { - int maxPhotoCount = 1; // 최소값을 1로 설정하여 나눗셈에서 0을 방지 - long maxTotalDuration = 1; // 최소값을 1로 설정하여 나눗셈에서 0을 방지 - - for (Person person : people) { - if (person.getPhotoCount() > maxPhotoCount) { - maxPhotoCount = person.getPhotoCount(); - } - if (person.getTotalDuration() > maxTotalDuration) { - maxTotalDuration = person.getTotalDuration(); - } - } - - for (Person person : people) { - Log.d("RANK", person.getInputName() + "의 총 통화량: " + person.getTotalDuration() - + "초 = " + person.getTotalDuration() / 60 + "분"); -// System.out.println(person.getInputName() + "의 총 통화량: " + person.getTotalDuration() + "초"); - double normalizedPhotoCount = (double) person.getPhotoCount() / maxPhotoCount; - double normalizedTotalDuration = (double) person.getTotalDuration() / maxTotalDuration; - double normalizedScore = (normalizedPhotoCount + normalizedTotalDuration) / 2.0; - person.setNormalizedScore(normalizedScore); - - // 정규화: 인물 친밀도 랭킹 테스트 로그 - Log.d("RANK"," 친밀도 점수: " + person.getNormalizedScore() + ", 사진 개수: " - +person.getPhotoCount()); -// System.out.println(" 친밀도 점수: " + person.getNormalizedScore() + ", 사진 개수: " -// +person.getPhotoCount()); - } - } - private void applyCurrentFilterAndSort() { - List filteredList = new ArrayList<>(allPeople); - - // 필터링 적용 - if (!currentFilterQuery.isEmpty()) { - filteredList = new ArrayList<>(); - for (Person person : allPeople) { - if (person.getInputName().toLowerCase().contains(currentFilterQuery.toLowerCase())) { - filteredList.add(person); - } - } - } - - // 정렬 적용 - switch (currentSortOption) { - case 0: - filteredList.sort((p1, p2) -> p1.getInputName().compareToIgnoreCase(p2.getInputName())); - break; - case 1: - filteredList.sort((p1, p2) -> Integer.compare(p2.getPhotoCount(), p1.getPhotoCount())); - break; - case 2: - List homeDisplayList = new ArrayList<>(); - for (Person person : allPeople) { - if (person.isHomeDisplay()) { - homeDisplayList.add(person); - } - } - normalizeScores(homeDisplayList); - homeDisplayList.sort((p1, p2) -> Double.compare(p2.getNormalizedScore(), p1.getNormalizedScore())); - - homeDisplayPeopleLiveData.setValue(homeDisplayList); - filteredPeopleLiveData.setValue(homeDisplayList); - // 홈 화면에 표시할 인물 로그 - for (Person person : homeDisplayList) { - Log.d("RANK", "홈 화면에 표시할 인물: " + person.getInputName()); - } - return; - } - - filteredPeopleLiveData.setValue(filteredList); - } - public void filterPeople(String query) { - currentFilterQuery = query; - applyCurrentFilterAndSort(); - } - - public void deletePerson(Person person) { - databaseHelper.deletePersonByName(person.getInputName()); - fetchPeopleFromLocalDatabase(); - } - - public void sortAlphabetical() { - currentSortOption = 0; - applyCurrentFilterAndSort(); - } - - public void sortByPhotoCount() { - currentSortOption = 1; - applyCurrentFilterAndSort(); - } - - public void filterHomeScreen() { - currentSortOption = 2; - applyCurrentFilterAndSort(); - } - - private void fetchPersonFrequencies() { - List persons = databaseHelper.getUniquePersons(); - if (!persons.isEmpty()) { - webRequestManager.getPersonFrequency(DatabaseUtils.getPersistentDeviceDatabaseName(getApplication()), persons, this); - } - } - - @Override - public void onPersonFrequencyUploadSuccess(PersonFrequencyResponse responses) { - for (PersonFrequencyResponse.Frequency frequency : responses.getFrequencies()) { - for (Person person : allPeople) { - if (person.getInputName().equals(frequency.getPersonName())) { - Log.d("RANK", person.getInputName()); - person.setPhotoCount(frequency.getFrequency()); - } - } - } - applyCurrentFilterAndSort(); - } - - @Override - public void onPersonFrequencyUploadFailure(String message) { - // 실패 처리 로직 추가 가능 - } -} diff --git a/app/src/main/java/com/example/metasearch/utils/DatabaseUtils.java b/app/src/main/java/com/example/metasearch/utils/DatabaseUtils.java deleted file mode 100644 index 1b6e398f..00000000 --- a/app/src/main/java/com/example/metasearch/utils/DatabaseUtils.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.example.metasearch.utils; - -import android.content.Context; -import android.content.SharedPreferences; - -import java.util.UUID; - -public class DatabaseUtils { - private static final String PREFERENCES_FILE = "persistent_device_prefs"; - private static final String UNIQUE_ID_KEY = "unique_id"; - - public static String getPersistentDeviceDatabaseName(Context context) { - String uniqueId = getOrCreateUniqueId(context); - return "db" + uniqueId; - } - - private static String getOrCreateUniqueId(Context context) { - SharedPreferences preferences = context.getSharedPreferences(PREFERENCES_FILE, Context.MODE_PRIVATE); - String uniqueId = preferences.getString(UNIQUE_ID_KEY, null); - - if (uniqueId == null) { - // UUID가 없으면 새로 생성하고 저장 - uniqueId = UUID.randomUUID().toString().replaceAll("-", ""); - SharedPreferences.Editor editor = preferences.edit(); - editor.putString(UNIQUE_ID_KEY, uniqueId); - editor.apply(); // 또는 commit() - } - - return uniqueId; - } -} diff --git a/app/src/main/java/com/example/metasearch/utils/GalleryImageManager.java b/app/src/main/java/com/example/metasearch/utils/GalleryImageManager.java deleted file mode 100644 index 11c8b746..00000000 --- a/app/src/main/java/com/example/metasearch/utils/GalleryImageManager.java +++ /dev/null @@ -1,162 +0,0 @@ -package com.example.metasearch.utils; - -import android.annotation.SuppressLint; -import android.content.ContentUris; -import android.content.Context; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.net.Uri; -import android.provider.MediaStore; - -import java.io.ByteArrayOutputStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class GalleryImageManager { - //갤러리에서 모든 이미지의 실제 경로를 가져오는 메소드 - public static ArrayList getAllGalleryImagesUriToString(Context context) { - ArrayList imagePaths = new ArrayList<>(); - Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; - String[] projection = {MediaStore.Images.Media.DATA}; - Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null); - if (cursor != null) { - while (cursor.moveToNext()) { - String imagePath = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)); - imagePaths.add(imagePath); - } - cursor.close(); - } - return imagePaths; - } - public static List getAllGalleryImagesUri(Context context) { - List imageUris = new ArrayList<>(); - Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; - - // MIME 타입을 필터링하여 JPEG 및 PNG 이미지만 조회 - String[] projection = {MediaStore.Images.Media._ID}; - String selection = MediaStore.Images.Media.MIME_TYPE + "=? OR " + MediaStore.Images.Media.MIME_TYPE + "=?"; - String[] selectionArgs = new String[] {"image/jpeg", "image/png"}; - - Cursor cursor = context.getContentResolver().query( - uri, - projection, - selection, - selectionArgs, - null - ); - if (cursor != null) { - int idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID); - while (cursor.moveToNext()) { - long id = cursor.getLong(idColumn); - Uri imageUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id); - imageUris.add(imageUri); - } - cursor.close(); - } - return imageUris; - } - // 갤러리에서 모든 이미지의 URI를 가져오는 메서드 -// public static List getAllGalleryImagesUri(Context context) { -// List imageUris = new ArrayList<>(); -// String[] projection = {MediaStore.Images.Media._ID}; -// Cursor cursor = context.getContentResolver().query( -// MediaStore.Images.Media.EXTERNAL_CONTENT_URI, -// projection, -// null, -// null, -// null -// ); -// if (cursor != null) { -// int idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID); -// while (cursor.moveToNext()) { -// long id = cursor.getLong(idColumn); -// Uri uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id); -// imageUris.add(uri); -// } -// cursor.close(); -// } -// return imageUris; -// } - // 갤러리에서 모든 이미지의 URI와 파일 이름을 매핑하여 가져오는 메서드 - public static Map getAllGalleryImagesUriWithName(Context context) { - Map images = new HashMap<>(); - Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; - String[] projection = {MediaStore.Images.Media._ID, MediaStore.Images.Media.DISPLAY_NAME}; - try (Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null)) { - while (cursor.moveToNext()) { - @SuppressLint("Range") long id = cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media._ID)); - @SuppressLint("Range") String name = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME)); - Uri contentUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id); - images.put(name, contentUri); - } - } - return images; - } - // 갤러리에서 모든 이미지의 이름을 확장자 없이 가져오는 메서드 - public static List getAllImageNamesWithoutExtension(Context context) { - List imageNamesWithoutExtension = new ArrayList<>(); - Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; - String[] projection = {MediaStore.Images.Media.DISPLAY_NAME}; - try (Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null)) { - while (true) { - assert cursor != null; - if (!cursor.moveToNext()) break; - @SuppressLint("Range") String imageNameWithExtension = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME)); - String imageNameWithoutExtension = imageNameWithExtension.substring(0, imageNameWithExtension.lastIndexOf('.')); - imageNamesWithoutExtension.add(imageNameWithoutExtension); - } - } catch (Exception e) { - e.printStackTrace(); - } - return imageNamesWithoutExtension; - } - // Neo4j 서버에서 받은 이름과 갤러리의 이름을 비교하여 일치하는 URI만 리스트로 반환하는 메서드 - public static List findMatchedUris(List photoNamesFromServer, Context context) { - List matchedUris = new ArrayList<>(); - Map allGalleryUrisWithName = getAllGalleryImagesUriWithName(context); - - for (String photoName : photoNamesFromServer) { - if (allGalleryUrisWithName.containsKey(photoName)) { - Uri matchedUri = allGalleryUrisWithName.get(photoName); - if (matchedUri != null) { - matchedUris.add(matchedUri); - } - } - } - return matchedUris; - } - - // Neo4j 서버에서 받은 이름과 갤러리의 이름을 비교하여 일치하는 URI만 반환하는 메서드 - public static Uri findMatchedUri(String photoNameFromServer, Context context) { - Map allGalleryUrisWithName = getAllGalleryImagesUriWithName(context); - - // photoNameFromServer가 Map의 키로 존재하는지 확인하고 해당 URI 반환 - if (allGalleryUrisWithName.containsKey(photoNameFromServer)) { - return allGalleryUrisWithName.get(photoNameFromServer); - } - // 일치하는 사진이 없을 경우 null 반환 - return null; - } - // 갤러리에서 URI에 해당하는 파일 이름을 찾는 메서드 - public static String getFileNameFromUri(Context context, Uri imageUri) { - String fileName = null; - String[] projection = {MediaStore.Images.Media.DISPLAY_NAME}; - try (Cursor cursor = context.getContentResolver().query(imageUri, projection, null, null, null)) { - if (cursor != null && cursor.moveToFirst()) { - int columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME); - fileName = cursor.getString(columnIndex); - } - } catch (Exception e) { - e.printStackTrace(); - } - return fileName; - } - public static byte[] getBytes(Bitmap bitmap) { // 이미지를 바이트 배열로 변환하는 예시 코드 - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - // 압축 품질 변경 - bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream); - return stream.toByteArray(); - } -} diff --git a/app/src/main/java/com/example/metasearch/utils/HttpHelper.java b/app/src/main/java/com/example/metasearch/utils/HttpHelper.java deleted file mode 100644 index bc0bdc13..00000000 --- a/app/src/main/java/com/example/metasearch/utils/HttpHelper.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.example.metasearch.utils; - -import com.example.metasearch.network.api.ApiService; - -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -import okhttp3.OkHttpClient; -import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; -/** - * API 서비스 인스턴스를 생성하기 위한 헬퍼 클래스 - * 여러 베이스 URL에 대해 각각의 Retrofit 인스턴스를 관리하기 위해 싱글톤 패턴 적용 - */ -public class HttpHelper { - // 각 baseUrl에 대응하는 HttpHelper 인스턴스를 저장하기 위한 Map - private static Map instances = new HashMap<>(); - private Retrofit retrofit; - /** - * 생성자를 private으로 선언하여 외부에서 직접 인스턴스를 생성하지 못하게 함 - * @param baseUrl API 요청을 보낼 기본 URL - */ - private HttpHelper(String baseUrl) { - // OkHttpClient 설정: 연결, 읽기, 쓰기 타임아웃 설정 - OkHttpClient okHttpClient = new OkHttpClient.Builder() - .connectTimeout(6000, TimeUnit.SECONDS) - .readTimeout(6000, TimeUnit.SECONDS) - .writeTimeout(6000, TimeUnit.SECONDS) - .build(); - // Retrofit 인스턴스 생성 - retrofit = new Retrofit.Builder() - .baseUrl(baseUrl) - .addConverterFactory(GsonConverterFactory.create()) - .client(okHttpClient) - .build(); - } - /** - * baseUrl에 해당하는 HttpHelper 인스턴스 반환 - * 인스턴스가 없을 경우 새로 생성하여 반환 - * @param baseUrl API 요청을 보낼 기본 URL - * @return HttpHelper 인스턴스 - */ - public static synchronized HttpHelper getInstance(String baseUrl) { - if (!instances.containsKey(baseUrl)) { - instances.put(baseUrl, new HttpHelper(baseUrl)); - } - return instances.get(baseUrl); - } - /** - * Retrofit 인스턴스 반환 - * @return Retrofit 인스턴스 - */ - public Retrofit getRetrofit() { - return retrofit; - } - /** - * 주어진 API 서비스 인터페이스에 대한 인스턴스 생성 - * @param apiServiceClass API 서비스 인터페이스의 클래스 객체 - * @return API 서비스 인터페이스의 인스턴스 - */ - public ApiService create(Class apiServiceClass) { - return retrofit.create(apiServiceClass); - } -} diff --git a/app/src/main/java/com/example/metasearch/utils/Neo4jDatabaseManager.java b/app/src/main/java/com/example/metasearch/utils/Neo4jDatabaseManager.java deleted file mode 100644 index 5861b912..00000000 --- a/app/src/main/java/com/example/metasearch/utils/Neo4jDatabaseManager.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.example.metasearch.utils; - -import java.util.List; - -public class Neo4jDatabaseManager { - // 사이퍼쿼리 생성 함수 : relationship과 entity를 모두 아는 경우 - public static String createCypherQuery(List entities, List relationships, int count) { - StringBuilder query = new StringBuilder("MATCH "); - for (int i = 0; i < count; i++) { -// query.append("(photo)-[:").append(relationships.get(i)).append("]->(entity {name: \"").append(entities.get(i)).append("\"})"); - query.append("(photo)-[:").append(relationships.get(i)).append("]->(a").append(i).append(":Entity {name: \"").append(entities.get(i)).append("\"})"); - if (i < count - 1) { - query.append(", \n"); - } - } - query.append("\n RETURN photo.name AS PhotoName"); - // 반환 형식 - // MATCH (photo)-[:RELATIONSHIP_1]->(a1:Entity {name: "ENTITY_1"}), - // (photo)-[:RELATIONSHIP_2]->(a2:Entity {name: "ENTITY_2"}) - // RETURN photo.name AS PhotoName - return query.toString(); - } - - // 사이퍼쿼리 생성 함수 : relationship만 아는 경우 - public static String createCypherQueryForRelationships(List relationships) { - StringBuilder query = new StringBuilder("MATCH "); - for (int i = 0; i < relationships.size(); i++) { - query.append("(photo)-[:").append(relationships.get(i)).append("]->(entity)"); - if (i < relationships.size() - 1) { - query.append(", "); - } - } - query.append(" RETURN photo.name AS PhotoName"); - // 반환 형식 - // MATCH - //(photo)-[:RELATIONSHIP_1]->(entity), - //(photo)-[:RELATIONSHIP_2]->(entity), - //... - //RETURN photo.name AS PhotoName - return query.toString(); - } - // 사이퍼쿼리 생성 함수 : entity만 아는 경우 - public static String createCypherQueryForEntities(List entities) { - StringBuilder query = new StringBuilder("MATCH "); - for (int i = 0; i < entities.size(); i++) { - query.append("(photo)-[]->(a").append(i).append(" {name: '").append(entities.get(i)).append("'})"); - if (i < entities.size() - 1) { - query.append(", "); - } - } - query.append(" RETURN DISTINCT photo.name AS PhotoName"); - // 반환 형식 - // MATCH - //(photo)-[]->(a1 {name: $entity1}), - //(photo)-[]->(a2 {name: $entity2}), - //... - //RETURN DISTINCT photo.name AS PhotoName - return query.toString(); - } - // String entityQuery = "MATCH (photo)-[]->(person {name: $entity2}) RETURN DISTINCT photo.name AS PhotoName"; -} diff --git a/app/src/main/java/com/example/metasearch/utils/UriToFileConverter.java b/app/src/main/java/com/example/metasearch/utils/UriToFileConverter.java deleted file mode 100644 index 8e23fb89..00000000 --- a/app/src/main/java/com/example/metasearch/utils/UriToFileConverter.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.example.metasearch.utils; - -import android.content.Context; -import android.net.Uri; -import android.util.Log; -import android.webkit.MimeTypeMap; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; - -public class UriToFileConverter { - - public static File getFileFromUri(Context context, Uri uri) throws IOException { - InputStream inputStream = context.getContentResolver().openInputStream(uri); - if (inputStream == null) { - throw new IOException("Unable to open input stream from URI"); - } - - File tempFile = createTemporaryFile(context, uri); - FileOutputStream outputStream = new FileOutputStream(tempFile); - - try { - byte[] buf = new byte[2048]; - int len; - while ((len = inputStream.read(buf)) > 0) { - outputStream.write(buf, 0, len); - } - } finally { - try { - inputStream.close(); - } catch (IOException e) { - Log.e("UriToFileConverter", "Error closing InputStream", e); - } - try { - outputStream.close(); - } catch (IOException e) { - Log.e("UriToFileConverter", "Error closing FileOutputStream", e); - } - } - - return tempFile; - } - - private static File createTemporaryFile(Context context, Uri uri) { - // Use file extension if possible - String fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri.toString()); - fileExtension = fileExtension.isEmpty() ? "tmp" : "." + fileExtension; - // Create a temporary file in the app's cache directory - File tempFile = new File(context.getCacheDir(), "tempFile" + fileExtension); - tempFile.deleteOnExit(); - return tempFile; - } -} diff --git a/app/src/main/java/com/metasearch/android/App.kt b/app/src/main/java/com/metasearch/android/App.kt new file mode 100644 index 00000000..a83edf89 --- /dev/null +++ b/app/src/main/java/com/metasearch/android/App.kt @@ -0,0 +1,7 @@ +package com.metasearch.android + +import android.app.Application +import dagger.hilt.android.HiltAndroidApp + +@HiltAndroidApp +class App : Application() diff --git a/app/src/main/java/com/metasearch/android/di/CircuitModule.kt b/app/src/main/java/com/metasearch/android/di/CircuitModule.kt new file mode 100644 index 00000000..2d24163c --- /dev/null +++ b/app/src/main/java/com/metasearch/android/di/CircuitModule.kt @@ -0,0 +1,32 @@ +package com.metasearch.android.di + +import com.slack.circuit.foundation.Circuit +import com.slack.circuit.runtime.presenter.Presenter +import com.slack.circuit.runtime.ui.Ui +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ActivityRetainedComponent +import dagger.hilt.android.scopes.ActivityRetainedScoped +import dagger.multibindings.Multibinds + +@Module +@InstallIn(ActivityRetainedComponent::class) +abstract class CircuitModule { + @Multibinds + abstract fun presenterFactories(): Set + + @Multibinds + abstract fun uiFactories(): Set + + companion object { + @[Provides ActivityRetainedScoped] + fun provideCircuit( + presenterFactories: @JvmSuppressWildcards Set, + uiFactories: @JvmSuppressWildcards Set, + ): Circuit = Circuit.Builder() + .addPresenterFactories(presenterFactories) + .addUiFactories(uiFactories) + .build() + } +} diff --git a/app/src/main/res/drawable/baseline_image_search_24.xml b/app/src/main/res/drawable/baseline_image_search_24.xml deleted file mode 100644 index d797b739..00000000 --- a/app/src/main/res/drawable/baseline_image_search_24.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/bottom_nav_colors.xml b/app/src/main/res/drawable/bottom_nav_colors.xml deleted file mode 100644 index b52c6cf2..00000000 --- a/app/src/main/res/drawable/bottom_nav_colors.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/dialog_background.xml b/app/src/main/res/drawable/dialog_background.xml deleted file mode 100644 index f89cdbd2..00000000 --- a/app/src/main/res/drawable/dialog_background.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/edittext_rounded_background.xml b/app/src/main/res/drawable/edittext_rounded_background.xml deleted file mode 100644 index c88d8215..00000000 --- a/app/src/main/res/drawable/edittext_rounded_background.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index 07d5da9c..00000000 --- a/app/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/icon_comment.xml b/app/src/main/res/drawable/icon_comment.xml deleted file mode 100644 index 822a7fbe..00000000 --- a/app/src/main/res/drawable/icon_comment.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/src/main/res/drawable/icon_delete.xml b/app/src/main/res/drawable/icon_delete.xml deleted file mode 100644 index bb70ec17..00000000 --- a/app/src/main/res/drawable/icon_delete.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/src/main/res/drawable/icon_down.xml b/app/src/main/res/drawable/icon_down.xml deleted file mode 100644 index 3c05ca41..00000000 --- a/app/src/main/res/drawable/icon_down.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/src/main/res/drawable/icon_graph.xml b/app/src/main/res/drawable/icon_graph.xml deleted file mode 100644 index 7fe20dad..00000000 --- a/app/src/main/res/drawable/icon_graph.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/src/main/res/drawable/icon_home.xml b/app/src/main/res/drawable/icon_home.xml deleted file mode 100644 index 57fa97a4..00000000 --- a/app/src/main/res/drawable/icon_home.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/src/main/res/drawable/icon_info.xml b/app/src/main/res/drawable/icon_info.xml deleted file mode 100644 index 2c2f5b64..00000000 --- a/app/src/main/res/drawable/icon_info.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/src/main/res/drawable/icon_like.xml b/app/src/main/res/drawable/icon_like.xml deleted file mode 100644 index 44516e33..00000000 --- a/app/src/main/res/drawable/icon_like.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/src/main/res/drawable/icon_pen.xml b/app/src/main/res/drawable/icon_pen.xml deleted file mode 100644 index e457d04b..00000000 --- a/app/src/main/res/drawable/icon_pen.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/src/main/res/drawable/icon_person.xml b/app/src/main/res/drawable/icon_person.xml deleted file mode 100644 index 98151c63..00000000 --- a/app/src/main/res/drawable/icon_person.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/src/main/res/drawable/icon_phone_call.xml b/app/src/main/res/drawable/icon_phone_call.xml deleted file mode 100644 index 59b5a80a..00000000 --- a/app/src/main/res/drawable/icon_phone_call.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/src/main/res/drawable/icon_refresh.xml b/app/src/main/res/drawable/icon_refresh.xml deleted file mode 100644 index 25ed29b0..00000000 --- a/app/src/main/res/drawable/icon_refresh.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/src/main/res/drawable/icon_search.xml b/app/src/main/res/drawable/icon_search.xml deleted file mode 100644 index 349ccfcb..00000000 --- a/app/src/main/res/drawable/icon_search.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/src/main/res/drawable/icon_search2.xml b/app/src/main/res/drawable/icon_search2.xml deleted file mode 100644 index a005dabe..00000000 --- a/app/src/main/res/drawable/icon_search2.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/src/main/res/drawable/icon_share.xml b/app/src/main/res/drawable/icon_share.xml deleted file mode 100644 index 50d64972..00000000 --- a/app/src/main/res/drawable/icon_share.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/src/main/res/drawable/icon_up.xml b/app/src/main/res/drawable/icon_up.xml deleted file mode 100644 index da0eb717..00000000 --- a/app/src/main/res/drawable/icon_up.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/src/main/res/drawable/icon_x.xml b/app/src/main/res/drawable/icon_x.xml deleted file mode 100644 index d0cb5adf..00000000 --- a/app/src/main/res/drawable/icon_x.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/src/main/res/drawable/image_analyze_alarm_background.xml b/app/src/main/res/drawable/image_analyze_alarm_background.xml deleted file mode 100644 index 85d28119..00000000 --- a/app/src/main/res/drawable/image_analyze_alarm_background.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ok_button_background.xml b/app/src/main/res/drawable/ok_button_background.xml deleted file mode 100644 index 41777f90..00000000 --- a/app/src/main/res/drawable/ok_button_background.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/rounded_button.xml b/app/src/main/res/drawable/rounded_button.xml deleted file mode 100644 index 1d15963f..00000000 --- a/app/src/main/res/drawable/rounded_button.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/rounded_menu.xml b/app/src/main/res/drawable/rounded_menu.xml deleted file mode 100644 index 148363a3..00000000 --- a/app/src/main/res/drawable/rounded_menu.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/scrollbar_vertical_thumb.xml b/app/src/main/res/drawable/scrollbar_vertical_thumb.xml deleted file mode 100644 index 68a17c58..00000000 --- a/app/src/main/res/drawable/scrollbar_vertical_thumb.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/table_cell_background.xml b/app/src/main/res/drawable/table_cell_background.xml deleted file mode 100644 index 1d5aefa9..00000000 --- a/app/src/main/res/drawable/table_cell_background.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_circle_to_search.xml b/app/src/main/res/layout/activity_circle_to_search.xml deleted file mode 100644 index 1066bd9f..00000000 --- a/app/src/main/res/layout/activity_circle_to_search.xml +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/activity_graph_display.xml b/app/src/main/res/layout/activity_graph_display.xml deleted file mode 100644 index 48149579..00000000 --- a/app/src/main/res/layout/activity_graph_display.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_image_display.xml b/app/src/main/res/layout/activity_image_display.xml deleted file mode 100644 index c9ab71d4..00000000 --- a/app/src/main/res/layout/activity_image_display.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index a1456658..00000000 --- a/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_person_photos.xml b/app/src/main/res/layout/activity_person_photos.xml deleted file mode 100644 index 6b02a42b..00000000 --- a/app/src/main/res/layout/activity_person_photos.xml +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - - - - -