thomas commited on
Commit ·
8ab7dfe
1
Parent(s): ba9c5ca
feature(#16): merging other repos(android & extension)
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +1 -0
- .github/workflows/android-release.yml +130 -0
- .github/workflows/android.yml +55 -0
- .github/workflows/extension_main.yml +78 -0
- Android/.gitignore +17 -0
- Android/README.md +48 -0
- Android/app/.gitignore +1 -0
- Android/app/build.gradle +113 -0
- Android/app/proguard-rules.pro +21 -0
- Android/app/src/androidTest/java/com/matthaigh27/chatgptwrapper/ExampleInstrumentedTest.kt +26 -0
- Android/app/src/main/AndroidManifest.xml +48 -0
- Android/app/src/main/gpt_icon-playstore.png +0 -0
- Android/app/src/main/java/com/matthaigh27/chatgptwrapper/MyApplication.kt +105 -0
- Android/app/src/main/java/com/matthaigh27/chatgptwrapper/activites/HomeActivity.kt +92 -0
- Android/app/src/main/java/com/matthaigh27/chatgptwrapper/adapters/ChatAdapter.kt +337 -0
- Android/app/src/main/java/com/matthaigh27/chatgptwrapper/database/MyDatabase.kt +38 -0
- Android/app/src/main/java/com/matthaigh27/chatgptwrapper/database/dao/ContactDao.kt +20 -0
- Android/app/src/main/java/com/matthaigh27/chatgptwrapper/database/dao/ImageDao.kt +20 -0
- Android/app/src/main/java/com/matthaigh27/chatgptwrapper/database/entity/ContactEntity.kt +11 -0
- Android/app/src/main/java/com/matthaigh27/chatgptwrapper/database/entity/ImageEntity.kt +11 -0
- Android/app/src/main/java/com/matthaigh27/chatgptwrapper/dialogs/CommonConfirmDialog.kt +74 -0
- Android/app/src/main/java/com/matthaigh27/chatgptwrapper/fragments/ChatFragment.kt +888 -0
- Android/app/src/main/java/com/matthaigh27/chatgptwrapper/models/common/ContactModel.kt +19 -0
- Android/app/src/main/java/com/matthaigh27/chatgptwrapper/models/common/HelpCommandModel.kt +10 -0
- Android/app/src/main/java/com/matthaigh27/chatgptwrapper/models/common/HelpPromptModel.kt +25 -0
- Android/app/src/main/java/com/matthaigh27/chatgptwrapper/models/common/ImagePromptModel.kt +11 -0
- Android/app/src/main/java/com/matthaigh27/chatgptwrapper/models/requestmodels/RequestBodyModel.kt +86 -0
- Android/app/src/main/java/com/matthaigh27/chatgptwrapper/models/requestmodels/RequestTrainContactModel.kt +79 -0
- Android/app/src/main/java/com/matthaigh27/chatgptwrapper/models/viewmodels/ChatMessageModel.kt +18 -0
- Android/app/src/main/java/com/matthaigh27/chatgptwrapper/services/MessageService.kt +32 -0
- Android/app/src/main/java/com/matthaigh27/chatgptwrapper/services/api/HttpClient.kt +117 -0
- Android/app/src/main/java/com/matthaigh27/chatgptwrapper/services/api/HttpRisingInterface.kt +7 -0
- Android/app/src/main/java/com/matthaigh27/chatgptwrapper/utils/Constants.java +63 -0
- Android/app/src/main/java/com/matthaigh27/chatgptwrapper/utils/ImageHelper.java +44 -0
- Android/app/src/main/java/com/matthaigh27/chatgptwrapper/utils/ReqType.kt +10 -0
- Android/app/src/main/java/com/matthaigh27/chatgptwrapper/utils/Utils.kt +356 -0
- Android/app/src/main/java/com/matthaigh27/chatgptwrapper/widgets/ContactDetailItem.kt +113 -0
- Android/app/src/main/java/com/matthaigh27/chatgptwrapper/widgets/ContactDetailWidget.kt +82 -0
- Android/app/src/main/java/com/matthaigh27/chatgptwrapper/widgets/HelpCommandEditText.kt +44 -0
- Android/app/src/main/java/com/matthaigh27/chatgptwrapper/widgets/HelpPromptWidget.kt +98 -0
- Android/app/src/main/java/com/matthaigh27/chatgptwrapper/widgets/ImagePickerWidget.kt +53 -0
- Android/app/src/main/java/com/matthaigh27/chatgptwrapper/widgets/SearchContactWidget.kt +62 -0
- Android/app/src/main/java/com/matthaigh27/chatgptwrapper/widgets/SmsEditorWidget.kt +77 -0
- Android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +30 -0
- Android/app/src/main/res/drawable/background_common_rounded_button.xml +6 -0
- Android/app/src/main/res/drawable/background_content_top.xml +8 -0
- Android/app/src/main/res/drawable/background_control_button_common.xml +6 -0
- Android/app/src/main/res/drawable/background_dialog_common_confirm.xml +6 -0
- Android/app/src/main/res/drawable/background_feedback_layout.xml +8 -0
- Android/app/src/main/res/drawable/background_load_assets_button_cancel.xml +6 -0
.gitattributes
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
*.gif filter=lfs diff=lfs merge=lfs -text
|
.github/workflows/android-release.yml
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Android Release
|
| 2 |
+
env:
|
| 3 |
+
main_project_module: app
|
| 4 |
+
on:
|
| 5 |
+
push:
|
| 6 |
+
tags:
|
| 7 |
+
- '*'
|
| 8 |
+
workflow_dispatch:
|
| 9 |
+
jobs:
|
| 10 |
+
build:
|
| 11 |
+
permissions: write-all
|
| 12 |
+
runs-on: ubuntu-latest
|
| 13 |
+
|
| 14 |
+
steps:
|
| 15 |
+
- uses: actions/checkout@v3
|
| 16 |
+
# Set Current Date As Env Variable
|
| 17 |
+
- name: Set current date as env variable
|
| 18 |
+
run: |
|
| 19 |
+
cd Android
|
| 20 |
+
echo "date_today=$(date +'%Y-%m-%d')" >> $GITHUB_ENV
|
| 21 |
+
|
| 22 |
+
# Set Repository Name As Env Variable
|
| 23 |
+
- name: Set repository name as env variable
|
| 24 |
+
run: |
|
| 25 |
+
cd Android
|
| 26 |
+
echo "repository_name=$(echo '${{ github.repository }}' | awk -F '/' '{print $2}')" >> $GITHUB_ENV
|
| 27 |
+
|
| 28 |
+
- name: set up JDK 11
|
| 29 |
+
uses: actions/setup-java@v3
|
| 30 |
+
with:
|
| 31 |
+
java-version: '11'
|
| 32 |
+
distribution: 'temurin'
|
| 33 |
+
cache: gradle
|
| 34 |
+
|
| 35 |
+
- name: Create file
|
| 36 |
+
run: |
|
| 37 |
+
cd Android
|
| 38 |
+
cat /home/runner/work/RisingPhone/RisingPhone/app/google-services.json | base64
|
| 39 |
+
|
| 40 |
+
- name: Putting data
|
| 41 |
+
env:
|
| 42 |
+
DATA: ${{ secrets.GOOGLE_SERVICES_JSON }}
|
| 43 |
+
run: |
|
| 44 |
+
cd Android
|
| 45 |
+
echo $DATA > /home/runner/work/RisingPhone/RisingPhone/app/google-services.json
|
| 46 |
+
|
| 47 |
+
- name: Grant execute permission for gradlew
|
| 48 |
+
run: |
|
| 49 |
+
cd Android
|
| 50 |
+
chmod +x gradlew
|
| 51 |
+
|
| 52 |
+
# Run Tests Build
|
| 53 |
+
- name: Run gradle tests
|
| 54 |
+
run: |
|
| 55 |
+
cd Android
|
| 56 |
+
./gradlew test
|
| 57 |
+
|
| 58 |
+
- name: Build with Gradle
|
| 59 |
+
run: |
|
| 60 |
+
cd Android
|
| 61 |
+
./gradlew build
|
| 62 |
+
|
| 63 |
+
- name: Archive lint results
|
| 64 |
+
if: always()
|
| 65 |
+
uses: actions/upload-artifact@v3
|
| 66 |
+
with:
|
| 67 |
+
name: lint-report
|
| 68 |
+
path: app/build/reports/lint-results-debug.html
|
| 69 |
+
|
| 70 |
+
# Create APK Debug
|
| 71 |
+
- name: Build apk debug project (APK) - ${{ env.main_project_module }} module
|
| 72 |
+
run: |
|
| 73 |
+
cd Android
|
| 74 |
+
./gradlew assembleDebug
|
| 75 |
+
|
| 76 |
+
# Create APK Release
|
| 77 |
+
- name: Build apk release project (APK) - ${{ env.main_project_module }} module
|
| 78 |
+
run: |
|
| 79 |
+
cd Android
|
| 80 |
+
./gradlew assemble
|
| 81 |
+
|
| 82 |
+
# Create Bundle AAB Release
|
| 83 |
+
# Noted for main module build [main_project_module]:bundleRelease
|
| 84 |
+
- name: Build app bundle release (AAB) - ${{ env.main_project_module }} module
|
| 85 |
+
run: |
|
| 86 |
+
cd Android
|
| 87 |
+
./gradlew ${{ env.main_project_module }}:bundleRelease
|
| 88 |
+
|
| 89 |
+
# Upload Artifact Build
|
| 90 |
+
# Noted For Output [main_project_module]/build/outputs/apk/debug/
|
| 91 |
+
- name: Upload APK Debug - ${{ env.repository_name }}
|
| 92 |
+
uses: actions/upload-artifact@v2
|
| 93 |
+
with:
|
| 94 |
+
name: ${{ env.date_today }} - ${{ env.playstore_name }} - ${{ env.repository_name }} - APK(s) debug generated
|
| 95 |
+
path: ${{ env.main_project_module }}/build/outputs/apk/debug/
|
| 96 |
+
|
| 97 |
+
# Noted For Output [main_project_module]/build/outputs/apk/release/
|
| 98 |
+
- name: Upload APK Release - ${{ env.repository_name }}
|
| 99 |
+
uses: actions/upload-artifact@v2
|
| 100 |
+
with:
|
| 101 |
+
name: ${{ env.date_today }} - ${{ env.playstore_name }} - ${{ env.repository_name }} - APK(s) release generated
|
| 102 |
+
path: ${{ env.main_project_module }}/build/outputs/apk/release/
|
| 103 |
+
|
| 104 |
+
# Noted For Output [main_project_module]/build/outputs/bundle/release/
|
| 105 |
+
- name: Upload AAB (App Bundle) Release - ${{ env.repository_name }}
|
| 106 |
+
uses: actions/upload-artifact@v2
|
| 107 |
+
with:
|
| 108 |
+
name: ${{ env.date_today }} - ${{ env.playstore_name }} - ${{ env.repository_name }} - App bundle(s) AAB release generated
|
| 109 |
+
path: ${{ env.main_project_module }}/build/outputs/bundle/release/
|
| 110 |
+
|
| 111 |
+
- name: Create Release
|
| 112 |
+
id: create_release
|
| 113 |
+
uses: actions/create-release@v1
|
| 114 |
+
env:
|
| 115 |
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
| 116 |
+
with:
|
| 117 |
+
tag_name: ${{ github.ref }}
|
| 118 |
+
release_name: Release ${{ github.ref }}
|
| 119 |
+
draft: false
|
| 120 |
+
prerelease: false
|
| 121 |
+
|
| 122 |
+
- name: Upload Release Asset
|
| 123 |
+
uses: actions/upload-release-asset@v1
|
| 124 |
+
env:
|
| 125 |
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
| 126 |
+
with:
|
| 127 |
+
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
| 128 |
+
asset_path: ${{ env.main_project_module }}/build/outputs/apk/debug/app-debug.apk # Update this with the correct APK path
|
| 129 |
+
asset_name: app-release-unsigned.apk
|
| 130 |
+
asset_content_type: application/vnd.android.package-archive
|
.github/workflows/android.yml
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Android CI
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches: [ "main", "develop" ]
|
| 6 |
+
pull_request:
|
| 7 |
+
branches: [ "main" ]
|
| 8 |
+
workflow_dispatch:
|
| 9 |
+
jobs:
|
| 10 |
+
build:
|
| 11 |
+
|
| 12 |
+
runs-on: ubuntu-latest
|
| 13 |
+
|
| 14 |
+
steps:
|
| 15 |
+
- uses: actions/checkout@v3
|
| 16 |
+
- name: set up JDK 11
|
| 17 |
+
uses: actions/setup-java@v3
|
| 18 |
+
with:
|
| 19 |
+
java-version: '11'
|
| 20 |
+
distribution: 'temurin'
|
| 21 |
+
cache: gradle
|
| 22 |
+
|
| 23 |
+
- name: Create file
|
| 24 |
+
run: |
|
| 25 |
+
cd Android
|
| 26 |
+
cat /home/runner/work/RisingPhone/RisingPhone/app/google-services.json | base64
|
| 27 |
+
|
| 28 |
+
- name: Putting data
|
| 29 |
+
env:
|
| 30 |
+
DATA: ${{ secrets.GOOGLE_SERVICES_JSON }}
|
| 31 |
+
run: |
|
| 32 |
+
cd Android
|
| 33 |
+
echo $DATA > /home/runner/work/RisingPhone/RisingPhone/app/google-services.json
|
| 34 |
+
|
| 35 |
+
- name: Grant execute permission for gradlew
|
| 36 |
+
run: |
|
| 37 |
+
cd Android
|
| 38 |
+
chmod +x gradlew
|
| 39 |
+
# Run Tests Build
|
| 40 |
+
- name: Run gradle tests
|
| 41 |
+
run: |
|
| 42 |
+
cd Android
|
| 43 |
+
./gradlew test
|
| 44 |
+
|
| 45 |
+
- name: Build with Gradle
|
| 46 |
+
run: |
|
| 47 |
+
cd Android
|
| 48 |
+
./gradlew build
|
| 49 |
+
|
| 50 |
+
- name: Archive lint results
|
| 51 |
+
if: always()
|
| 52 |
+
uses: actions/upload-artifact@v3
|
| 53 |
+
with:
|
| 54 |
+
name: lint-report
|
| 55 |
+
path: app/build/reports/lint-results-debug.html
|
.github/workflows/extension_main.yml
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Development
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
pull_request:
|
| 5 |
+
types:
|
| 6 |
+
- opened
|
| 7 |
+
- edited
|
| 8 |
+
- synchronize
|
| 9 |
+
- reopened
|
| 10 |
+
workflow_call:
|
| 11 |
+
|
| 12 |
+
jobs:
|
| 13 |
+
test:
|
| 14 |
+
name: Test application
|
| 15 |
+
runs-on: ubuntu-22.04
|
| 16 |
+
timeout-minutes: 10
|
| 17 |
+
steps:
|
| 18 |
+
- name: "☁️ checkout repository"
|
| 19 |
+
uses: actions/checkout@v2
|
| 20 |
+
|
| 21 |
+
- name: "🔧 setup node"
|
| 22 |
+
uses: actions/setup-node@v2.1.5
|
| 23 |
+
with:
|
| 24 |
+
node-version: 16
|
| 25 |
+
|
| 26 |
+
- name: "🔧 install npm@latest"
|
| 27 |
+
run: |
|
| 28 |
+
cd Extension
|
| 29 |
+
npm i -g npm@latest
|
| 30 |
+
|
| 31 |
+
- name: "📦 install dependencies"
|
| 32 |
+
uses: bahmutov/npm-install@v1
|
| 33 |
+
|
| 34 |
+
- name: "🔍 run tests"
|
| 35 |
+
run: |
|
| 36 |
+
cd Extension
|
| 37 |
+
npm run test --if-present
|
| 38 |
+
|
| 39 |
+
lint:
|
| 40 |
+
name: Code standards
|
| 41 |
+
runs-on: ubuntu-22.04
|
| 42 |
+
timeout-minutes: 10
|
| 43 |
+
steps:
|
| 44 |
+
- name: "☁️ checkout repository"
|
| 45 |
+
uses: actions/checkout@v2
|
| 46 |
+
|
| 47 |
+
- name: "🔧 setup node"
|
| 48 |
+
uses: actions/setup-node@v2.1.5
|
| 49 |
+
with:
|
| 50 |
+
node-version: 16
|
| 51 |
+
|
| 52 |
+
- name: "🔧 install npm@latest"
|
| 53 |
+
run: |
|
| 54 |
+
cd Extension
|
| 55 |
+
npm i -g npm@latest
|
| 56 |
+
|
| 57 |
+
- name: "📦 install dependencies"
|
| 58 |
+
uses: bahmutov/npm-install@v1
|
| 59 |
+
|
| 60 |
+
- name: "🔍 lint code"
|
| 61 |
+
run: |
|
| 62 |
+
cd Extension
|
| 63 |
+
npm run lint --if-present
|
| 64 |
+
|
| 65 |
+
build:
|
| 66 |
+
runs-on: ubuntu-22.04
|
| 67 |
+
|
| 68 |
+
steps:
|
| 69 |
+
- uses: actions/checkout@v3
|
| 70 |
+
- uses: actions/setup-node@v3
|
| 71 |
+
with:
|
| 72 |
+
node-version: 16
|
| 73 |
+
- run: |
|
| 74 |
+
cd Extension
|
| 75 |
+
npm ci
|
| 76 |
+
- run: |
|
| 77 |
+
cd Extension
|
| 78 |
+
npm run build
|
Android/.gitignore
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.iml
|
| 2 |
+
.gradle
|
| 3 |
+
/local.properties
|
| 4 |
+
/.idea
|
| 5 |
+
/.idea/caches
|
| 6 |
+
/.idea/libraries
|
| 7 |
+
/.idea/modules.xml
|
| 8 |
+
/.idea/workspace.xml
|
| 9 |
+
/.idea/navEditor.xml
|
| 10 |
+
/.idea/assetWizardSettings.xml
|
| 11 |
+
.DS_Store
|
| 12 |
+
/build
|
| 13 |
+
/captures
|
| 14 |
+
.externalNativeBuild
|
| 15 |
+
.cxx
|
| 16 |
+
local.properties
|
| 17 |
+
/app/google-services.json
|
Android/README.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# RisingAndroid
|
| 2 |
+
[](https://github.com/ttt246/RisingPhone/actions/workflows/android.yml)
|
| 3 |
+
[](https://github.com/ttt246/RisingPhone/actions/workflows/android.yml)
|
| 4 |
+
|
| 5 |
+
All complex software including operating systems will need to be rewritten from the ground up to take advantage of machine learning. In our OS, a AI will manage all apps via plugins, which can be prompted by the user. Our plugins can run as an openai plugin, or in our backend.
|
| 6 |
+
|
| 7 |
+
### RisingPhone
|
| 8 |
+
|
| 9 |
+
<p align='center'>
|
| 10 |
+
<img align='center' src='assets/img/desc.png' width='100%' />
|
| 11 |
+
</p>
|
| 12 |
+
|
| 13 |
+
- 📱 Support for mobile devices to manage all apps via plugin as its launcher.
|
| 14 |
+
- 🔗 Multiple API support (Web API for Free and Plus users, GPT-3.5, GPT-4, etc.).
|
| 15 |
+
- 🔍 Integration to all mainstream search engines, and custom queries to support additional sites.
|
| 16 |
+
|
| 17 |
+
### Features
|
| 18 |
+
|
| 19 |
+
| Title | Description |
|
| 20 |
+
| ------------ | ------------ |
|
| 21 |
+
| General Chat | Users can chat with AI plugins. |
|
| 22 |
+
| Open Browser Automatically | If a user is going to open browser, the app opens browser and search what a user wants automatically |
|
| 23 |
+
| Image Search System | A user can search image on Android local storage |
|
| 24 |
+
| Send SMS | If a user says that send SMS, mobile open SMS editor and a user can send SMS using the editor. |
|
| 25 |
+
|
| 26 |
+
[[Rising Brain](https://github.com/ttt246/RisingBrain)]
|
| 27 |
+
### Run locally
|
| 28 |
+
- Copy google-services.json into app folder of project
|
| 29 |
+
|
| 30 |
+
### CI/CD
|
| 31 |
+
- set google-services.json to github secrets
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
### Test
|
| 35 |
+
- Unit Test
|
| 36 |
+
- Instrumented Test
|
| 37 |
+
|
| 38 |
+
### Contributing
|
| 39 |
+
|
| 40 |
+
Please refer to each project's style and contribution guidelines for submitting patches and additions. In general, we follow the "fork-and-pull" Git workflow.
|
| 41 |
+
|
| 42 |
+
1. **Fork** the repo on GitHub
|
| 43 |
+
2. **Clone** the project to your own machine
|
| 44 |
+
3. **Commit** changes to your own branch
|
| 45 |
+
4. **Push** your work back up to your fork
|
| 46 |
+
5. Submit a **Pull request** so that we can review your changes
|
| 47 |
+
|
| 48 |
+
NOTE: Be sure to merge the latest from "upstream" before making a pull request!
|
Android/app/.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
/build
|
Android/app/build.gradle
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
plugins {
|
| 2 |
+
id 'com.android.application'
|
| 3 |
+
id 'kotlin-android'
|
| 4 |
+
id 'com.google.gms.google-services'
|
| 5 |
+
id 'kotlin-kapt'
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
android {
|
| 9 |
+
namespace 'com.matthaigh27.chatgptwrapper'
|
| 10 |
+
compileSdk 33
|
| 11 |
+
|
| 12 |
+
defaultConfig {
|
| 13 |
+
applicationId "com.matthaigh27.chatgptwrapper"
|
| 14 |
+
minSdk 28
|
| 15 |
+
targetSdk 33
|
| 16 |
+
versionCode 7
|
| 17 |
+
versionName "1.5"
|
| 18 |
+
|
| 19 |
+
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
buildTypes {
|
| 23 |
+
debug {
|
| 24 |
+
buildConfigField "String", "BASE_URL", "\"https://smartphone.herokuapp.com/\""
|
| 25 |
+
}
|
| 26 |
+
release {
|
| 27 |
+
// Use your desired server address for the release version
|
| 28 |
+
buildConfigField "String", "BASE_URL", "\"https://chatgptphone.herokuapp.com/\""
|
| 29 |
+
|
| 30 |
+
minifyEnabled true
|
| 31 |
+
shrinkResources true
|
| 32 |
+
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
| 33 |
+
}
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
buildFeatures {
|
| 37 |
+
viewBinding true
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
compileOptions {
|
| 41 |
+
sourceCompatibility JavaVersion.VERSION_11
|
| 42 |
+
targetCompatibility JavaVersion.VERSION_11
|
| 43 |
+
}
|
| 44 |
+
kotlinOptions {
|
| 45 |
+
jvmTarget = JavaVersion.VERSION_11
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
dataBinding {
|
| 49 |
+
enabled true
|
| 50 |
+
}
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
dependencies {
|
| 54 |
+
// App dependencies
|
| 55 |
+
implementation 'androidx.core:core-ktx:1.9.0'
|
| 56 |
+
implementation 'androidx.appcompat:appcompat:1.6.0'
|
| 57 |
+
implementation 'com.google.android.material:material:1.8.0'
|
| 58 |
+
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
| 59 |
+
|
| 60 |
+
implementation platform('com.google.firebase:firebase-bom:31.4.0')
|
| 61 |
+
implementation 'com.google.android.gms:play-services-gcm:17.0.0'
|
| 62 |
+
|
| 63 |
+
implementation 'com.google.firebase:firebase-messaging'
|
| 64 |
+
implementation 'com.google.firebase:firebase-analytics'
|
| 65 |
+
implementation 'com.google.firebase:firebase-firestore-ktx:24.4.5'
|
| 66 |
+
implementation 'com.google.firebase:firebase-firestore:15.0.0'
|
| 67 |
+
|
| 68 |
+
implementation 'com.google.firebase:firebase-messaging-ktx'
|
| 69 |
+
implementation 'com.google.firebase:firebase-analytics-ktx'
|
| 70 |
+
implementation 'com.firebaseui:firebase-ui-storage:7.2.0'
|
| 71 |
+
|
| 72 |
+
implementation 'com.squareup.okhttp3:okhttp:3.0.1'
|
| 73 |
+
implementation 'com.github.soulqw:CoCo:1.1.2'
|
| 74 |
+
implementation 'com.github.dhaval2404:imagepicker:2.1'
|
| 75 |
+
implementation 'com.google.firebase:firebase-storage-ktx:20.1.0'
|
| 76 |
+
testImplementation 'org.testng:testng:6.9.6'
|
| 77 |
+
|
| 78 |
+
// Testing-only dependencies
|
| 79 |
+
androidTestImplementation 'androidx.test:core:' + rootProject.coreVersion;
|
| 80 |
+
androidTestImplementation 'androidx.test.ext:junit:' + rootProject.extJUnitVersion;
|
| 81 |
+
androidTestImplementation 'androidx.test:runner:' + rootProject.runnerVersion;
|
| 82 |
+
|
| 83 |
+
testImplementation 'junit:junit:4.13.2'
|
| 84 |
+
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
| 85 |
+
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
| 86 |
+
|
| 87 |
+
// UiAutomator Testing
|
| 88 |
+
androidTestImplementation 'androidx.test.uiautomator:uiautomator:' + rootProject.uiAutomatorVersion;
|
| 89 |
+
androidTestImplementation 'org.hamcrest:hamcrest-integration:1.3'
|
| 90 |
+
|
| 91 |
+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2"
|
| 92 |
+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2"
|
| 93 |
+
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-alpha05"
|
| 94 |
+
|
| 95 |
+
implementation 'com.github.bumptech.glide:glide:4.12.0'
|
| 96 |
+
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
|
| 97 |
+
|
| 98 |
+
implementation 'com.google.code.gson:gson:2.8.5'
|
| 99 |
+
|
| 100 |
+
implementation "androidx.room:room-runtime:$room_version"
|
| 101 |
+
annotationProcessor "androidx.room:room-compiler:$room_version"
|
| 102 |
+
|
| 103 |
+
// To use room database
|
| 104 |
+
implementation "androidx.room:room-ktx:$room_version"
|
| 105 |
+
kapt "androidx.room:room-compiler:$room_version"
|
| 106 |
+
implementation "androidx.room:room-rxjava2:$room_version"
|
| 107 |
+
implementation "androidx.room:room-rxjava3:$room_version"
|
| 108 |
+
implementation "androidx.room:room-guava:$room_version"
|
| 109 |
+
testImplementation "androidx.room:room-testing:$room_version"
|
| 110 |
+
implementation "androidx.room:room-paging:$room_version"
|
| 111 |
+
|
| 112 |
+
implementation 'de.hdodenhof:circleimageview:3.1.0'
|
| 113 |
+
}
|
Android/app/proguard-rules.pro
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Add project specific ProGuard rules here.
|
| 2 |
+
# You can control the set of applied configuration files using the
|
| 3 |
+
# proguardFiles setting in build.gradle.
|
| 4 |
+
#
|
| 5 |
+
# For more details, see
|
| 6 |
+
# http://developer.android.com/guide/developing/tools/proguard.html
|
| 7 |
+
|
| 8 |
+
# If your project uses WebView with JS, uncomment the following
|
| 9 |
+
# and specify the fully qualified class name to the JavaScript interface
|
| 10 |
+
# class:
|
| 11 |
+
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
| 12 |
+
# public *;
|
| 13 |
+
#}
|
| 14 |
+
|
| 15 |
+
# Uncomment this to preserve the line number information for
|
| 16 |
+
# debugging stack traces.
|
| 17 |
+
#-keepattributes SourceFile,LineNumberTable
|
| 18 |
+
|
| 19 |
+
# If you keep the line number information, uncomment this to
|
| 20 |
+
# hide the original source file name.
|
| 21 |
+
#-renamesourcefileattribute SourceFile
|
Android/app/src/androidTest/java/com/matthaigh27/chatgptwrapper/ExampleInstrumentedTest.kt
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.matthaigh27.chatgptwrapper
|
| 2 |
+
|
| 3 |
+
import androidx.test.platform.app.InstrumentationRegistry
|
| 4 |
+
import androidx.test.ext.junit.runners.AndroidJUnit4
|
| 5 |
+
|
| 6 |
+
import org.junit.Test
|
| 7 |
+
import org.junit.runner.RunWith
|
| 8 |
+
|
| 9 |
+
import org.junit.Assert.*
|
| 10 |
+
|
| 11 |
+
/**
|
| 12 |
+
* Instrumented test, which will execute on an Android device.
|
| 13 |
+
*
|
| 14 |
+
* See [testing documentation](http://d.android.com/tools/testing).
|
| 15 |
+
*/
|
| 16 |
+
@RunWith(AndroidJUnit4::class)
|
| 17 |
+
class
|
| 18 |
+
|
| 19 |
+
ExampleInstrumentedTest {
|
| 20 |
+
@Test
|
| 21 |
+
fun useAppContext() {
|
| 22 |
+
// Context of the app under test.
|
| 23 |
+
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
| 24 |
+
assertEquals("com.matthaigh27.chatgptwrapper", appContext.packageName)
|
| 25 |
+
}
|
| 26 |
+
}
|
Android/app/src/main/AndroidManifest.xml
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="utf-8"?>
|
| 2 |
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
| 3 |
+
package="com.matthaigh27.chatgptwrapper">
|
| 4 |
+
|
| 5 |
+
<uses-permission android:name="android.permission.INTERNET" />
|
| 6 |
+
<uses-permission android:name="android.permission.MICROPHONE" />
|
| 7 |
+
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
| 8 |
+
<uses-permission android:name="android.permission.LAUNCHER" />
|
| 9 |
+
<uses-permission android:name="android.permission.GET_PACKAGE_SIZE" />
|
| 10 |
+
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
| 11 |
+
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
| 12 |
+
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
| 13 |
+
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
| 14 |
+
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
| 15 |
+
<uses-permission android:name="android.permission.READ_CONTACTS"/>
|
| 16 |
+
<uses-permission android:name="android.permission.SEND_SMS"/>
|
| 17 |
+
<uses-permission android:name="android.permission.CALL_PHONE" />
|
| 18 |
+
|
| 19 |
+
<uses-feature android:name="android.hardware.audio.low_latency" />
|
| 20 |
+
<uses-feature android:name="android.hardware.audio.pro" />
|
| 21 |
+
<uses-feature android:name="android.hardware.microphone" />
|
| 22 |
+
|
| 23 |
+
<application
|
| 24 |
+
android:name=".MyApplication"
|
| 25 |
+
android:hardwareAccelerated="true"
|
| 26 |
+
android:icon="@mipmap/gpt_icon"
|
| 27 |
+
android:label="@string/app_name"
|
| 28 |
+
android:roundIcon="@mipmap/gpt_icon_round"
|
| 29 |
+
android:supportsRtl="true"
|
| 30 |
+
android:theme="@style/Theme.Design.NoActionBar">
|
| 31 |
+
<activity
|
| 32 |
+
android:name=".activites.HomeActivity"
|
| 33 |
+
android:windowSoftInputMode="adjustResize"
|
| 34 |
+
android:exported="true">
|
| 35 |
+
<intent-filter>
|
| 36 |
+
<action android:name="android.intent.action.MAIN" />
|
| 37 |
+
<category android:name="android.intent.category.LAUNCHER" />
|
| 38 |
+
</intent-filter>
|
| 39 |
+
</activity>
|
| 40 |
+
<service
|
| 41 |
+
android:name=".services.MessageService"
|
| 42 |
+
android:exported="false">
|
| 43 |
+
<intent-filter>
|
| 44 |
+
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
| 45 |
+
</intent-filter>
|
| 46 |
+
</service>
|
| 47 |
+
</application>
|
| 48 |
+
</manifest>
|
Android/app/src/main/gpt_icon-playstore.png
ADDED
|
|
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/MyApplication.kt
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.matthaigh27.chatgptwrapper
|
| 2 |
+
|
| 3 |
+
import android.Manifest
|
| 4 |
+
import android.annotation.SuppressLint
|
| 5 |
+
import android.app.Application
|
| 6 |
+
import android.app.NotificationChannel
|
| 7 |
+
import android.app.NotificationManager
|
| 8 |
+
import android.content.pm.PackageManager
|
| 9 |
+
import android.util.Log
|
| 10 |
+
import com.google.android.gms.tasks.OnCompleteListener
|
| 11 |
+
import com.google.firebase.messaging.FirebaseMessaging
|
| 12 |
+
import com.matthaigh27.chatgptwrapper.utils.Constants
|
| 13 |
+
import android.provider.Settings
|
| 14 |
+
import androidx.core.app.ActivityCompat
|
| 15 |
+
import androidx.core.app.NotificationCompat
|
| 16 |
+
import androidx.core.app.NotificationManagerCompat
|
| 17 |
+
import java.util.Random
|
| 18 |
+
|
| 19 |
+
class MyApplication : Application() {
|
| 20 |
+
|
| 21 |
+
private var mFCMToken: String = String()
|
| 22 |
+
private var mUUID: String = String()
|
| 23 |
+
@SuppressLint("HardwareIds")
|
| 24 |
+
override fun onCreate() {
|
| 25 |
+
super.onCreate()
|
| 26 |
+
|
| 27 |
+
initToken()
|
| 28 |
+
// on below line we are getting device id.
|
| 29 |
+
mUUID = Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID)
|
| 30 |
+
appContext = applicationContext as MyApplication
|
| 31 |
+
|
| 32 |
+
Log.v("risingandroid mUUID: ", mUUID)
|
| 33 |
+
Log.v("risingandroid FCMToken: ", mFCMToken)
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
private fun initToken() {
|
| 37 |
+
FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task ->
|
| 38 |
+
if (!task.isSuccessful) {
|
| 39 |
+
Log.w(Constants.TAG, "Fetching FCM registration token failed", task.exception)
|
| 40 |
+
return@OnCompleteListener
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
/**
|
| 44 |
+
* Get new FCM registration token
|
| 45 |
+
*/
|
| 46 |
+
|
| 47 |
+
/**
|
| 48 |
+
* Get new FCM registration token
|
| 49 |
+
*/
|
| 50 |
+
mFCMToken = task.result
|
| 51 |
+
Log.d(Constants.TAG, mFCMToken)
|
| 52 |
+
})
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
fun getFCMToken(): String {
|
| 56 |
+
return this.mFCMToken
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
fun getUUID(): String {
|
| 60 |
+
return this.mUUID
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
/**
|
| 64 |
+
* this shows system notification with message
|
| 65 |
+
* @param message to be shown with system notification
|
| 66 |
+
*/
|
| 67 |
+
fun showNotification(message: String) {
|
| 68 |
+
val notificationId: Int = Random().nextInt()
|
| 69 |
+
val channelId = "chat_message"
|
| 70 |
+
|
| 71 |
+
val builder = NotificationCompat.Builder(this, channelId)
|
| 72 |
+
builder.setSmallIcon(R.drawable.ic_notification)
|
| 73 |
+
builder.setContentTitle(Constants.TAG)
|
| 74 |
+
builder.setContentText(message)
|
| 75 |
+
builder.setStyle(
|
| 76 |
+
NotificationCompat.BigTextStyle().bigText(
|
| 77 |
+
message
|
| 78 |
+
)
|
| 79 |
+
)
|
| 80 |
+
builder.priority = NotificationCompat.PRIORITY_DEFAULT
|
| 81 |
+
builder.setAutoCancel(true)
|
| 82 |
+
|
| 83 |
+
val channelName: CharSequence = "Chat Message"
|
| 84 |
+
val channelDescription = "This notification channel is used for chat message notifications"
|
| 85 |
+
val importance = NotificationManager.IMPORTANCE_DEFAULT
|
| 86 |
+
val channel = NotificationChannel(channelId, channelName, importance)
|
| 87 |
+
channel.description = channelDescription
|
| 88 |
+
val notificationManager = getSystemService(
|
| 89 |
+
NotificationManager::class.java
|
| 90 |
+
)
|
| 91 |
+
notificationManager.createNotificationChannel(channel)
|
| 92 |
+
val notificationManagerCompat = NotificationManagerCompat.from(this)
|
| 93 |
+
if (ActivityCompat.checkSelfPermission(
|
| 94 |
+
this, Manifest.permission.POST_NOTIFICATIONS
|
| 95 |
+
) != PackageManager.PERMISSION_GRANTED
|
| 96 |
+
) {
|
| 97 |
+
return
|
| 98 |
+
}
|
| 99 |
+
notificationManagerCompat.notify(notificationId, builder.build())
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
companion object {
|
| 103 |
+
lateinit var appContext: MyApplication
|
| 104 |
+
}
|
| 105 |
+
}
|
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/activites/HomeActivity.kt
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.matthaigh27.chatgptwrapper.activites
|
| 2 |
+
|
| 3 |
+
import android.Manifest
|
| 4 |
+
import android.annotation.SuppressLint
|
| 5 |
+
import android.content.pm.PackageManager
|
| 6 |
+
import android.os.Build
|
| 7 |
+
import android.os.Bundle
|
| 8 |
+
import android.util.Log
|
| 9 |
+
import androidx.appcompat.app.AlertDialog
|
| 10 |
+
import androidx.appcompat.app.AppCompatActivity
|
| 11 |
+
import com.google.android.material.tabs.TabLayout.TabGravity
|
| 12 |
+
import com.matthaigh27.chatgptwrapper.R
|
| 13 |
+
import com.matthaigh27.chatgptwrapper.dialogs.CommonConfirmDialog
|
| 14 |
+
import com.matthaigh27.chatgptwrapper.fragments.ChatFragment
|
| 15 |
+
import java.io.File
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class HomeActivity : AppCompatActivity() {
|
| 19 |
+
|
| 20 |
+
private val PERMISSIONS_REQUEST_CODE = 1
|
| 21 |
+
|
| 22 |
+
private val PERMISSIONS = arrayOf(
|
| 23 |
+
Manifest.permission.SEND_SMS,
|
| 24 |
+
Manifest.permission.READ_CONTACTS,
|
| 25 |
+
Manifest.permission.CALL_PHONE,
|
| 26 |
+
Manifest.permission.READ_EXTERNAL_STORAGE
|
| 27 |
+
)
|
| 28 |
+
|
| 29 |
+
override fun onCreate(savedInstanceState: Bundle?) {
|
| 30 |
+
super.onCreate(savedInstanceState)
|
| 31 |
+
setContentView(R.layout.activity_home)
|
| 32 |
+
|
| 33 |
+
requestPermission()
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
private fun requestPermission() {
|
| 37 |
+
val notGrantedPermissions = PERMISSIONS.filter {
|
| 38 |
+
checkSelfPermission(it) != PackageManager.PERMISSION_GRANTED
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
if (notGrantedPermissions.isNotEmpty()) {
|
| 42 |
+
if (shouldShowRequestPermissionRationale(notGrantedPermissions[0])) {
|
| 43 |
+
// show custom permission rationale
|
| 44 |
+
val confirmDialog = CommonConfirmDialog(this)
|
| 45 |
+
confirmDialog.setMessage("This app requires SMS, Contacts and Phone permissions to function properly. Please grant the necessary permissions.")
|
| 46 |
+
confirmDialog.setOnClickListener(object :
|
| 47 |
+
CommonConfirmDialog.OnConfirmButtonClickListener {
|
| 48 |
+
override fun onPositiveButtonClick() {
|
| 49 |
+
requestPermissions(
|
| 50 |
+
notGrantedPermissions.toTypedArray(), PERMISSIONS_REQUEST_CODE
|
| 51 |
+
)
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
override fun onNegativeButtonClick() {
|
| 55 |
+
finish()
|
| 56 |
+
}
|
| 57 |
+
})
|
| 58 |
+
confirmDialog.show()
|
| 59 |
+
} else {
|
| 60 |
+
requestPermissions(notGrantedPermissions.toTypedArray(), PERMISSIONS_REQUEST_CODE)
|
| 61 |
+
}
|
| 62 |
+
} else {
|
| 63 |
+
// Permissions already granted, navigate to your desired fragment
|
| 64 |
+
navigateToChatFragment()
|
| 65 |
+
}
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
private fun navigateToChatFragment() {
|
| 69 |
+
val fragmentTransaction = supportFragmentManager.beginTransaction()
|
| 70 |
+
fragmentTransaction.replace(R.id.frame_container, ChatFragment()).commit()
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
@SuppressLint("MissingSuperCall")
|
| 74 |
+
override fun onRequestPermissionsResult(
|
| 75 |
+
requestCode: Int, permissions: Array<out String>, grantResults: IntArray
|
| 76 |
+
) {
|
| 77 |
+
when (requestCode) {
|
| 78 |
+
PERMISSIONS_REQUEST_CODE -> {
|
| 79 |
+
if (grantResults.all { it == PackageManager.PERMISSION_GRANTED }) {
|
| 80 |
+
// Permissions granted, navigate to your desired fragment
|
| 81 |
+
navigateToChatFragment()
|
| 82 |
+
} else {
|
| 83 |
+
requestPermission()
|
| 84 |
+
}
|
| 85 |
+
return
|
| 86 |
+
}
|
| 87 |
+
}
|
| 88 |
+
}
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
|
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/adapters/ChatAdapter.kt
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.matthaigh27.chatgptwrapper.adapters
|
| 2 |
+
|
| 3 |
+
import android.annotation.SuppressLint
|
| 4 |
+
import android.app.Dialog
|
| 5 |
+
import android.content.Context
|
| 6 |
+
import android.graphics.Bitmap
|
| 7 |
+
import android.graphics.BitmapFactory
|
| 8 |
+
import android.view.LayoutInflater
|
| 9 |
+
import android.view.View
|
| 10 |
+
import android.view.ViewGroup
|
| 11 |
+
import android.widget.FrameLayout
|
| 12 |
+
import android.widget.ImageView
|
| 13 |
+
import android.widget.LinearLayout
|
| 14 |
+
import android.widget.TextView
|
| 15 |
+
import androidx.constraintlayout.widget.ConstraintLayout
|
| 16 |
+
import androidx.recyclerview.widget.RecyclerView
|
| 17 |
+
import com.matthaigh27.chatgptwrapper.R
|
| 18 |
+
import com.matthaigh27.chatgptwrapper.models.common.ContactModel
|
| 19 |
+
import com.matthaigh27.chatgptwrapper.models.common.HelpPromptModel
|
| 20 |
+
import com.matthaigh27.chatgptwrapper.models.viewmodels.ChatMessageModel
|
| 21 |
+
import com.matthaigh27.chatgptwrapper.utils.Constants.MSG_WIDGET_TYPE_HELP_PRMOPT
|
| 22 |
+
import com.matthaigh27.chatgptwrapper.utils.Constants.MSG_WIDGET_TYPE_SEARCH_CONTACT
|
| 23 |
+
import com.matthaigh27.chatgptwrapper.utils.Constants.MSG_WIDGET_TYPE_SMS
|
| 24 |
+
import com.matthaigh27.chatgptwrapper.utils.ImageHelper
|
| 25 |
+
import com.matthaigh27.chatgptwrapper.utils.Utils
|
| 26 |
+
import com.matthaigh27.chatgptwrapper.widgets.ContactDetailItem
|
| 27 |
+
import com.matthaigh27.chatgptwrapper.widgets.ContactDetailWidget
|
| 28 |
+
import com.matthaigh27.chatgptwrapper.widgets.HelpPromptWidget
|
| 29 |
+
import com.matthaigh27.chatgptwrapper.widgets.SearchContactWidget
|
| 30 |
+
import com.matthaigh27.chatgptwrapper.widgets.SmsEditorWidget
|
| 31 |
+
import org.json.JSONArray
|
| 32 |
+
|
| 33 |
+
class ChatAdapter(list: ArrayList<ChatMessageModel>, context: Context) :
|
| 34 |
+
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
| 35 |
+
|
| 36 |
+
private var mChatModelList: ArrayList<ChatMessageModel> = ArrayList()
|
| 37 |
+
private var mContext: Context
|
| 38 |
+
|
| 39 |
+
private var mListener: MessageWidgetListener? = null
|
| 40 |
+
var mOnSMSClickListener: ContactDetailItem.OnSMSClickListener? = null
|
| 41 |
+
|
| 42 |
+
private val feedbackData = arrayOf(
|
| 43 |
+
arrayOf(R.drawable.ic_thumb_up_disable, R.drawable.ic_thumb_down),
|
| 44 |
+
arrayOf(R.drawable.ic_thumb_up_disable, R.drawable.ic_thumb_down_disable),
|
| 45 |
+
arrayOf(R.drawable.ic_thumb_up, R.drawable.ic_thumb_down_disable),
|
| 46 |
+
)
|
| 47 |
+
|
| 48 |
+
init {
|
| 49 |
+
mChatModelList = list
|
| 50 |
+
mContext = context
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
| 54 |
+
val context = parent.context
|
| 55 |
+
val inflater = LayoutInflater.from(context)
|
| 56 |
+
|
| 57 |
+
/**
|
| 58 |
+
* Inflate the custom layout and Return a new holder instance
|
| 59 |
+
*/
|
| 60 |
+
return if (viewType == 0) {
|
| 61 |
+
SendMessageViewHolder(
|
| 62 |
+
inflater.inflate(
|
| 63 |
+
R.layout.item_container_sent_message, parent, false
|
| 64 |
+
)
|
| 65 |
+
)
|
| 66 |
+
} else {
|
| 67 |
+
ReceiveMessageViewHolder(
|
| 68 |
+
inflater.inflate(
|
| 69 |
+
R.layout.item_container_received_message, parent, false
|
| 70 |
+
)
|
| 71 |
+
)
|
| 72 |
+
}
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
/**
|
| 76 |
+
* Involves populating data into the item through holder
|
| 77 |
+
*/
|
| 78 |
+
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
| 79 |
+
/**
|
| 80 |
+
* Get the data model based on position
|
| 81 |
+
*/
|
| 82 |
+
val index = holder.adapterPosition
|
| 83 |
+
val messageModel: ChatMessageModel = mChatModelList[index]
|
| 84 |
+
if (messageModel.isMe) {
|
| 85 |
+
setSentMessageData(holder as SendMessageViewHolder, messageModel)
|
| 86 |
+
} else {
|
| 87 |
+
setReceiveMessageData(holder as ReceiveMessageViewHolder, messageModel)
|
| 88 |
+
}
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
private fun setSentMessageData(holder: SendMessageViewHolder, messageModel: ChatMessageModel) {
|
| 92 |
+
/**
|
| 93 |
+
* Set item views based on your views and data model
|
| 94 |
+
*/
|
| 95 |
+
if (messageModel.message.isEmpty()) {
|
| 96 |
+
holder.textMessage.visibility = View.GONE
|
| 97 |
+
} else {
|
| 98 |
+
holder.textMessage.text = messageModel.message
|
| 99 |
+
holder.textMessage.visibility = View.VISIBLE
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
if (messageModel.image != null) {
|
| 104 |
+
val radius = mContext.resources.getDimensionPixelSize(R.dimen.chat_message_item_radius)
|
| 105 |
+
|
| 106 |
+
val originBmp = BitmapFactory.decodeByteArray(messageModel.image, 0, messageModel.image!!.size)
|
| 107 |
+
val bmp = ImageHelper.getRoundedCornerBitmap(originBmp, radius)
|
| 108 |
+
holder.imgMessage.visibility = View.VISIBLE
|
| 109 |
+
holder.imgMessage.setImageBitmap(bmp)
|
| 110 |
+
holder.imgMessage.setOnClickListener {
|
| 111 |
+
onImageClick(originBmp)
|
| 112 |
+
}
|
| 113 |
+
} else {
|
| 114 |
+
holder.imgMessage.visibility = View.GONE
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
if (messageModel.isWidget) {
|
| 118 |
+
when (messageModel.widgetType) {
|
| 119 |
+
MSG_WIDGET_TYPE_HELP_PRMOPT -> {
|
| 120 |
+
val model: HelpPromptModel =
|
| 121 |
+
HelpPromptModel.initModelWithString(messageModel.widgetDescription)
|
| 122 |
+
val helpPromptWidget = HelpPromptWidget(mContext, model)
|
| 123 |
+
val helpPromptListener = object : HelpPromptWidget.OnHelpPromptListener {
|
| 124 |
+
override fun onSuccess(prompt: String) {
|
| 125 |
+
mChatModelList[holder.adapterPosition].isWidget = false
|
| 126 |
+
holder.llMessageWidget.visibility = View.GONE
|
| 127 |
+
holder.llMessageWidget.removeAllViews()
|
| 128 |
+
mListener!!.sentHelpPrompt(prompt)
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
override fun onCancel() {
|
| 132 |
+
mChatModelList[holder.adapterPosition].isWidget = false
|
| 133 |
+
holder.llMessageWidget.visibility = View.GONE
|
| 134 |
+
holder.llMessageWidget.removeAllViews()
|
| 135 |
+
mListener!!.canceledHelpPrompt()
|
| 136 |
+
}
|
| 137 |
+
}
|
| 138 |
+
helpPromptWidget.setOnClickListener(helpPromptListener)
|
| 139 |
+
holder.llMessageWidget.addView(helpPromptWidget)
|
| 140 |
+
holder.llMessageWidget.visibility = View.VISIBLE
|
| 141 |
+
}
|
| 142 |
+
}
|
| 143 |
+
} else {
|
| 144 |
+
holder.llMessageWidget.visibility = View.GONE
|
| 145 |
+
}
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
@SuppressLint("UseCompatLoadingForDrawables")
|
| 149 |
+
fun setReceiveMessageData(holder: ReceiveMessageViewHolder, messageModel: ChatMessageModel) {
|
| 150 |
+
/**
|
| 151 |
+
* Set item views based on your views and data model
|
| 152 |
+
*/
|
| 153 |
+
if (messageModel.message.isEmpty()) {
|
| 154 |
+
holder.textMessage.visibility = View.GONE
|
| 155 |
+
} else {
|
| 156 |
+
holder.textMessage.text = messageModel.message
|
| 157 |
+
holder.textMessage.visibility = View.VISIBLE
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
if (messageModel.image != null) {
|
| 162 |
+
val radius = mContext.resources.getDimensionPixelSize(R.dimen.chat_message_item_radius)
|
| 163 |
+
|
| 164 |
+
val originBmp = BitmapFactory.decodeByteArray(messageModel.image, 0, messageModel.image!!.size)
|
| 165 |
+
val bmp = ImageHelper.getRoundedCornerBitmap(originBmp, radius)
|
| 166 |
+
holder.imgMessage.visibility = View.VISIBLE
|
| 167 |
+
holder.imgMessage.setImageBitmap(bmp)
|
| 168 |
+
holder.imgMessage.setOnClickListener {
|
| 169 |
+
onImageClick(originBmp)
|
| 170 |
+
}
|
| 171 |
+
} else {
|
| 172 |
+
holder.imgMessage.visibility = View.GONE
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
holder.llFeedback.visibility = if (messageModel.visibleFeedback) {
|
| 176 |
+
View.VISIBLE
|
| 177 |
+
} else {
|
| 178 |
+
View.GONE
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
setThumb(holder)
|
| 182 |
+
|
| 183 |
+
holder.itemLayout.setOnLongClickListener {
|
| 184 |
+
if (holder.llFeedback.visibility == View.VISIBLE) {
|
| 185 |
+
holder.llFeedback.visibility = View.GONE
|
| 186 |
+
mChatModelList[holder.adapterPosition].visibleFeedback = false
|
| 187 |
+
} else {
|
| 188 |
+
holder.llFeedback.visibility = View.VISIBLE
|
| 189 |
+
mChatModelList[holder.adapterPosition].visibleFeedback = true
|
| 190 |
+
}
|
| 191 |
+
return@setOnLongClickListener true
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
holder.btnThumbUp.setOnClickListener {
|
| 195 |
+
mChatModelList[holder.adapterPosition].feedback = 1
|
| 196 |
+
setThumb(holder)
|
| 197 |
+
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
holder.btnThumbDown.setOnClickListener {
|
| 201 |
+
mChatModelList[holder.adapterPosition].feedback = -1
|
| 202 |
+
setThumb(holder)
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
if (messageModel.isWidget) {
|
| 206 |
+
holder.llContactWidget.removeAllViews()
|
| 207 |
+
when (messageModel.widgetType) {
|
| 208 |
+
MSG_WIDGET_TYPE_SMS -> {
|
| 209 |
+
val smsWidget = SmsEditorWidget(mContext, null)
|
| 210 |
+
if(messageModel.widgetDescription.isNotEmpty()) {
|
| 211 |
+
smsWidget.setToUserName(messageModel.widgetDescription)
|
| 212 |
+
}
|
| 213 |
+
holder.llMessageWidget.addView(smsWidget)
|
| 214 |
+
holder.llMessageWidget.visibility = View.VISIBLE
|
| 215 |
+
|
| 216 |
+
val smsListener = object : SmsEditorWidget.OnClickListener {
|
| 217 |
+
override fun confirmSMS(phonenumber: String, message: String) {
|
| 218 |
+
mChatModelList[holder.adapterPosition].isWidget = false
|
| 219 |
+
holder.llMessageWidget.visibility = View.GONE
|
| 220 |
+
holder.llMessageWidget.removeAllViews()
|
| 221 |
+
mListener!!.sentSMS(phonenumber, message)
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
override fun cancelSMS() {
|
| 225 |
+
mChatModelList[holder.adapterPosition].isWidget = false
|
| 226 |
+
holder.llMessageWidget.visibility = View.GONE
|
| 227 |
+
holder.llMessageWidget.removeAllViews()
|
| 228 |
+
mListener!!.canceledSMS()
|
| 229 |
+
}
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
smsWidget.setOnClickListener(smsListener)
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
MSG_WIDGET_TYPE_SEARCH_CONTACT -> {
|
| 236 |
+
val contacts = Utils.instance.getContacts(mContext)
|
| 237 |
+
|
| 238 |
+
val contactIds = JSONArray(messageModel.widgetDescription)
|
| 239 |
+
for (i in 0 until contactIds.length()) {
|
| 240 |
+
val contactId = contactIds[i].toString()
|
| 241 |
+
val contact = Utils.instance.getContactModelById(contactId, contacts)
|
| 242 |
+
|
| 243 |
+
val searchContactWidget = SearchContactWidget(mContext, contact, null)
|
| 244 |
+
searchContactWidget.mSMSOnClickListener = mOnSMSClickListener
|
| 245 |
+
holder.llContactWidget.addView(searchContactWidget)
|
| 246 |
+
}
|
| 247 |
+
holder.llContactWidget.visibility = View.VISIBLE
|
| 248 |
+
}
|
| 249 |
+
}
|
| 250 |
+
} else {
|
| 251 |
+
holder.llMessageWidget.visibility = View.GONE
|
| 252 |
+
holder.llContactWidget.visibility = View.GONE
|
| 253 |
+
}
|
| 254 |
+
}
|
| 255 |
+
|
| 256 |
+
@SuppressLint("UseCompatLoadingForDrawables")
|
| 257 |
+
fun setThumb(holder: ReceiveMessageViewHolder) {
|
| 258 |
+
holder.btnThumbUp.setImageDrawable(
|
| 259 |
+
mContext.getDrawable(
|
| 260 |
+
feedbackData[mChatModelList[holder.adapterPosition].feedback + 1][0]
|
| 261 |
+
)
|
| 262 |
+
)
|
| 263 |
+
holder.btnThumbDown.setImageDrawable(
|
| 264 |
+
mContext.getDrawable(
|
| 265 |
+
feedbackData[mChatModelList[holder.adapterPosition].feedback + 1][1]
|
| 266 |
+
)
|
| 267 |
+
)
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
/**
|
| 271 |
+
* Returns the total count of items in the list
|
| 272 |
+
*/
|
| 273 |
+
override fun getItemCount(): Int {
|
| 274 |
+
return mChatModelList.size
|
| 275 |
+
}
|
| 276 |
+
|
| 277 |
+
override fun getItemViewType(position: Int): Int {
|
| 278 |
+
return if (mChatModelList[position].isMe) 0 else 1
|
| 279 |
+
}
|
| 280 |
+
|
| 281 |
+
private fun onImageClick(bitmap: Bitmap) {
|
| 282 |
+
val dialog = Dialog(mContext)
|
| 283 |
+
dialog.setContentView(R.layout.view_full_image)
|
| 284 |
+
val fullImage = dialog.findViewById(R.id.fullImage) as ImageView
|
| 285 |
+
fullImage.setImageBitmap(bitmap)
|
| 286 |
+
dialog.show()
|
| 287 |
+
}
|
| 288 |
+
|
| 289 |
+
inner class ReceiveMessageViewHolder internal constructor(itemView: View) :
|
| 290 |
+
RecyclerView.ViewHolder(itemView) {
|
| 291 |
+
var textMessage: TextView
|
| 292 |
+
var imgMessage: ImageView
|
| 293 |
+
var llFeedback: LinearLayout
|
| 294 |
+
var btnThumbUp: ImageView
|
| 295 |
+
var btnThumbDown: ImageView
|
| 296 |
+
var itemLayout: ConstraintLayout
|
| 297 |
+
var llMessageWidget: LinearLayout
|
| 298 |
+
var llContactWidget: LinearLayout
|
| 299 |
+
|
| 300 |
+
init {
|
| 301 |
+
textMessage = itemView.findViewById<View>(R.id.textMessage) as TextView
|
| 302 |
+
imgMessage = itemView.findViewById<View>(R.id.imgMessage) as ImageView
|
| 303 |
+
btnThumbUp = itemView.findViewById<View>(R.id.btn_thumb_up) as ImageView
|
| 304 |
+
btnThumbDown = itemView.findViewById<View>(R.id.btn_thumb_down) as ImageView
|
| 305 |
+
llFeedback = itemView.findViewById<View>(R.id.ll_feedback) as LinearLayout
|
| 306 |
+
itemLayout = itemView.findViewById<View>(R.id.cl_receive_message) as ConstraintLayout
|
| 307 |
+
llMessageWidget = itemView.findViewById<View>(R.id.ll_message_widget) as LinearLayout
|
| 308 |
+
llContactWidget = itemView.findViewById<View>(R.id.ll_contacts_widget) as LinearLayout
|
| 309 |
+
}
|
| 310 |
+
}
|
| 311 |
+
|
| 312 |
+
inner class SendMessageViewHolder internal constructor(itemView: View) :
|
| 313 |
+
RecyclerView.ViewHolder(itemView) {
|
| 314 |
+
var textMessage: TextView
|
| 315 |
+
var imgMessage: ImageView
|
| 316 |
+
var itemLayout: ConstraintLayout
|
| 317 |
+
var llMessageWidget: LinearLayout
|
| 318 |
+
|
| 319 |
+
init {
|
| 320 |
+
textMessage = itemView.findViewById<View>(R.id.textMessage) as TextView
|
| 321 |
+
imgMessage = itemView.findViewById<View>(R.id.imgMessage) as ImageView
|
| 322 |
+
itemLayout = itemView.findViewById<View>(R.id.cl_sent_message) as ConstraintLayout
|
| 323 |
+
llMessageWidget = itemView.findViewById<View>(R.id.ll_message_widget) as LinearLayout
|
| 324 |
+
}
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
+
interface MessageWidgetListener {
|
| 328 |
+
fun sentSMS(phonenumber: String, message: String)
|
| 329 |
+
fun canceledSMS()
|
| 330 |
+
fun sentHelpPrompt(prompt: String)
|
| 331 |
+
fun canceledHelpPrompt()
|
| 332 |
+
}
|
| 333 |
+
|
| 334 |
+
fun setMessageWidgetListener(listener: MessageWidgetListener) {
|
| 335 |
+
mListener = listener
|
| 336 |
+
}
|
| 337 |
+
}
|
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/database/MyDatabase.kt
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.matthaigh27.chatgptwrapper.database
|
| 2 |
+
|
| 3 |
+
import android.content.Context
|
| 4 |
+
import androidx.room.Database
|
| 5 |
+
import androidx.room.Room
|
| 6 |
+
import androidx.room.RoomDatabase
|
| 7 |
+
import com.matthaigh27.chatgptwrapper.database.dao.ContactDao
|
| 8 |
+
import com.matthaigh27.chatgptwrapper.database.dao.ImageDao
|
| 9 |
+
import com.matthaigh27.chatgptwrapper.database.entity.ContactEntity
|
| 10 |
+
import com.matthaigh27.chatgptwrapper.database.entity.ImageEntity
|
| 11 |
+
|
| 12 |
+
@Database(entities = [ImageEntity::class, ContactEntity::class], version = 1, exportSchema = false)
|
| 13 |
+
abstract class MyDatabase : RoomDatabase() {
|
| 14 |
+
|
| 15 |
+
abstract fun imageDao(): ImageDao
|
| 16 |
+
abstract fun contactDao(): ContactDao
|
| 17 |
+
|
| 18 |
+
companion object {
|
| 19 |
+
@Volatile
|
| 20 |
+
private var INSTANCE: MyDatabase? = null
|
| 21 |
+
|
| 22 |
+
fun getDatabase(context: Context): MyDatabase {
|
| 23 |
+
val tempInstance = INSTANCE
|
| 24 |
+
if (tempInstance != null) {
|
| 25 |
+
return tempInstance
|
| 26 |
+
}
|
| 27 |
+
synchronized(this) {
|
| 28 |
+
val instance = Room.databaseBuilder(
|
| 29 |
+
context.applicationContext,
|
| 30 |
+
MyDatabase::class.java,
|
| 31 |
+
"risingphone_database"
|
| 32 |
+
).build()
|
| 33 |
+
INSTANCE = instance
|
| 34 |
+
return instance
|
| 35 |
+
}
|
| 36 |
+
}
|
| 37 |
+
}
|
| 38 |
+
}
|
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/database/dao/ContactDao.kt
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.matthaigh27.chatgptwrapper.database.dao
|
| 2 |
+
|
| 3 |
+
import androidx.lifecycle.LiveData
|
| 4 |
+
import androidx.room.*
|
| 5 |
+
import com.matthaigh27.chatgptwrapper.database.entity.ContactEntity
|
| 6 |
+
|
| 7 |
+
@Dao
|
| 8 |
+
interface ContactDao {
|
| 9 |
+
@Insert
|
| 10 |
+
suspend fun insertContact(contact: ContactEntity)
|
| 11 |
+
|
| 12 |
+
@Update
|
| 13 |
+
suspend fun updateContact(contact: ContactEntity)
|
| 14 |
+
|
| 15 |
+
@Delete
|
| 16 |
+
suspend fun deleteContact(contact: ContactEntity)
|
| 17 |
+
|
| 18 |
+
@Query("SELECT * FROM contacts")
|
| 19 |
+
fun getAllContacts(): List<ContactEntity>
|
| 20 |
+
}
|
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/database/dao/ImageDao.kt
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.matthaigh27.chatgptwrapper.database.dao
|
| 2 |
+
|
| 3 |
+
import androidx.lifecycle.LiveData
|
| 4 |
+
import androidx.room.*
|
| 5 |
+
import com.matthaigh27.chatgptwrapper.database.entity.ImageEntity
|
| 6 |
+
|
| 7 |
+
@Dao
|
| 8 |
+
interface ImageDao {
|
| 9 |
+
@Insert
|
| 10 |
+
suspend fun insertImage(image: ImageEntity)
|
| 11 |
+
|
| 12 |
+
@Update
|
| 13 |
+
suspend fun updateImage(image: ImageEntity)
|
| 14 |
+
|
| 15 |
+
@Delete
|
| 16 |
+
suspend fun deleteImage(image: ImageEntity)
|
| 17 |
+
|
| 18 |
+
@Query("SELECT * FROM images")
|
| 19 |
+
fun getAllImages(): List<ImageEntity>
|
| 20 |
+
}
|
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/database/entity/ContactEntity.kt
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.matthaigh27.chatgptwrapper.database.entity
|
| 2 |
+
|
| 3 |
+
import androidx.room.Entity
|
| 4 |
+
import androidx.room.PrimaryKey
|
| 5 |
+
|
| 6 |
+
@Entity(tableName = "contacts")
|
| 7 |
+
data class ContactEntity (
|
| 8 |
+
@PrimaryKey(autoGenerate = false) val id: String,
|
| 9 |
+
val name: String,
|
| 10 |
+
val phoneNumber: String,
|
| 11 |
+
)
|
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/database/entity/ImageEntity.kt
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.matthaigh27.chatgptwrapper.database.entity
|
| 2 |
+
|
| 3 |
+
import androidx.room.Entity
|
| 4 |
+
import androidx.room.PrimaryKey
|
| 5 |
+
|
| 6 |
+
@Entity(tableName = "images")
|
| 7 |
+
data class ImageEntity (
|
| 8 |
+
@PrimaryKey(autoGenerate = true) val id: Int,
|
| 9 |
+
val path: String,
|
| 10 |
+
val name: String,
|
| 11 |
+
)
|
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/dialogs/CommonConfirmDialog.kt
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.matthaigh27.chatgptwrapper.dialogs
|
| 2 |
+
|
| 3 |
+
import android.app.Dialog
|
| 4 |
+
import android.content.Context
|
| 5 |
+
import android.graphics.Color
|
| 6 |
+
import android.graphics.drawable.ColorDrawable
|
| 7 |
+
import android.os.Bundle
|
| 8 |
+
import android.view.View
|
| 9 |
+
import android.view.ViewGroup
|
| 10 |
+
import android.view.Window
|
| 11 |
+
import android.widget.Button
|
| 12 |
+
import android.widget.TextView
|
| 13 |
+
import com.matthaigh27.chatgptwrapper.R
|
| 14 |
+
|
| 15 |
+
class CommonConfirmDialog(context: Context) : Dialog(context), View.OnClickListener {
|
| 16 |
+
|
| 17 |
+
private var mTvMessage: TextView? = null
|
| 18 |
+
private var mMessage: String = ""
|
| 19 |
+
private lateinit var mClickListener: OnConfirmButtonClickListener
|
| 20 |
+
|
| 21 |
+
init {
|
| 22 |
+
setCancelable(false)
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
fun setOnClickListener(listener: OnConfirmButtonClickListener) {
|
| 26 |
+
mClickListener = listener
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
override fun onCreate(savedInstanceState: Bundle?) {
|
| 30 |
+
super.onCreate(savedInstanceState)
|
| 31 |
+
|
| 32 |
+
initView()
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
private fun initView() {
|
| 36 |
+
requestWindowFeature(Window.FEATURE_NO_TITLE)
|
| 37 |
+
setContentView(R.layout.dialog_common_confirm)
|
| 38 |
+
|
| 39 |
+
window!!.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
| 40 |
+
window!!.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
|
| 41 |
+
|
| 42 |
+
findViewById<Button>(R.id.btn_ok).setOnClickListener(this)
|
| 43 |
+
findViewById<Button>(R.id.btn_cancel).setOnClickListener(this)
|
| 44 |
+
|
| 45 |
+
mTvMessage = findViewById(R.id.tv_message)
|
| 46 |
+
|
| 47 |
+
setCanceledOnTouchOutside(true)
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
fun setMessage(message: String) {
|
| 51 |
+
mMessage = message
|
| 52 |
+
mTvMessage?.text = mMessage
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
override fun onClick(view: View?) {
|
| 57 |
+
when (view?.id) {
|
| 58 |
+
R.id.btn_ok -> {
|
| 59 |
+
mClickListener.onPositiveButtonClick()
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
R.id.btn_cancel -> {
|
| 63 |
+
mClickListener.onNegativeButtonClick()
|
| 64 |
+
}
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
this.dismiss()
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
interface OnConfirmButtonClickListener {
|
| 71 |
+
fun onPositiveButtonClick()
|
| 72 |
+
fun onNegativeButtonClick()
|
| 73 |
+
}
|
| 74 |
+
}
|
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/fragments/ChatFragment.kt
ADDED
|
@@ -0,0 +1,888 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.matthaigh27.chatgptwrapper.fragments
|
| 2 |
+
|
| 3 |
+
import android.os.Bundle
|
| 4 |
+
import androidx.fragment.app.Fragment
|
| 5 |
+
import android.view.LayoutInflater
|
| 6 |
+
import android.view.View
|
| 7 |
+
import android.view.ViewGroup
|
| 8 |
+
import android.annotation.SuppressLint
|
| 9 |
+
import android.app.ActionBar.LayoutParams
|
| 10 |
+
import android.content.*
|
| 11 |
+
import android.graphics.Bitmap
|
| 12 |
+
import android.graphics.BitmapFactory
|
| 13 |
+
import android.net.Uri
|
| 14 |
+
import android.os.Build
|
| 15 |
+
import android.os.StrictMode
|
| 16 |
+
import android.os.StrictMode.ThreadPolicy
|
| 17 |
+
import android.provider.MediaStore
|
| 18 |
+
import android.telephony.SmsManager
|
| 19 |
+
import android.util.Log
|
| 20 |
+
import android.view.KeyEvent
|
| 21 |
+
import android.view.View.OnClickListener
|
| 22 |
+
import android.view.animation.AccelerateDecelerateInterpolator
|
| 23 |
+
import android.view.animation.AlphaAnimation
|
| 24 |
+
import android.view.animation.Animation
|
| 25 |
+
import android.view.animation.LinearInterpolator
|
| 26 |
+
import android.view.animation.RotateAnimation
|
| 27 |
+
import android.view.animation.TranslateAnimation
|
| 28 |
+
import android.widget.*
|
| 29 |
+
import androidx.annotation.RequiresApi
|
| 30 |
+
import androidx.recyclerview.widget.LinearLayoutManager
|
| 31 |
+
import androidx.recyclerview.widget.RecyclerView
|
| 32 |
+
import com.google.firebase.storage.FirebaseStorage
|
| 33 |
+
import com.matthaigh27.chatgptwrapper.MyApplication
|
| 34 |
+
import com.matthaigh27.chatgptwrapper.R
|
| 35 |
+
import com.matthaigh27.chatgptwrapper.adapters.ChatAdapter
|
| 36 |
+
import com.matthaigh27.chatgptwrapper.database.MyDatabase
|
| 37 |
+
import com.matthaigh27.chatgptwrapper.database.entity.ImageEntity
|
| 38 |
+
import com.matthaigh27.chatgptwrapper.widgets.ImagePickerWidget
|
| 39 |
+
import com.matthaigh27.chatgptwrapper.widgets.ImagePickerWidget.OnPositiveButtonClickListener
|
| 40 |
+
import com.matthaigh27.chatgptwrapper.models.*
|
| 41 |
+
import com.matthaigh27.chatgptwrapper.models.common.HelpCommandModel
|
| 42 |
+
import com.matthaigh27.chatgptwrapper.models.common.HelpPromptModel
|
| 43 |
+
import com.matthaigh27.chatgptwrapper.models.viewmodels.ChatMessageModel
|
| 44 |
+
import com.matthaigh27.chatgptwrapper.services.api.HttpClient
|
| 45 |
+
import com.matthaigh27.chatgptwrapper.services.api.HttpRisingInterface
|
| 46 |
+
import com.matthaigh27.chatgptwrapper.utils.Constants.*
|
| 47 |
+
import com.matthaigh27.chatgptwrapper.utils.Utils
|
| 48 |
+
import com.matthaigh27.chatgptwrapper.widgets.ContactDetailItem
|
| 49 |
+
import com.qw.photo.CoCo
|
| 50 |
+
import com.qw.photo.callback.CoCoAdapter
|
| 51 |
+
import com.qw.photo.callback.CoCoCallBack
|
| 52 |
+
import com.qw.photo.constant.Range
|
| 53 |
+
import com.qw.photo.pojo.PickResult
|
| 54 |
+
import com.qw.photo.pojo.TakeResult
|
| 55 |
+
import kotlinx.coroutines.CoroutineScope
|
| 56 |
+
import kotlinx.coroutines.Dispatchers
|
| 57 |
+
import kotlinx.coroutines.launch
|
| 58 |
+
import okhttp3.*
|
| 59 |
+
import org.json.JSONException
|
| 60 |
+
import org.json.JSONObject
|
| 61 |
+
import java.io.*
|
| 62 |
+
import java.util.*
|
| 63 |
+
import kotlin.collections.ArrayList
|
| 64 |
+
|
| 65 |
+
class ChatFragment : Fragment(), OnClickListener, HttpRisingInterface {
|
| 66 |
+
private lateinit var rootView: View
|
| 67 |
+
private var mContext: Context? = null
|
| 68 |
+
|
| 69 |
+
/** ui components for chatlist recyclerview */
|
| 70 |
+
private lateinit var mRvChatList: RecyclerView
|
| 71 |
+
private lateinit var mEtMessage: EditText
|
| 72 |
+
|
| 73 |
+
/** ui components for loading spinner */
|
| 74 |
+
private lateinit var mSpLoading: ImageView
|
| 75 |
+
private lateinit var mTvLoading: TextView
|
| 76 |
+
private lateinit var mLlLoading: LinearLayout
|
| 77 |
+
|
| 78 |
+
/** ui components for loading photo */
|
| 79 |
+
private lateinit var mLlLoadPhoto: LinearLayout
|
| 80 |
+
private lateinit var mIvLoadedPhoto: ImageView
|
| 81 |
+
private lateinit var mBtnCancelLoadPhoto: TextView
|
| 82 |
+
|
| 83 |
+
/** adapter for chat recyclerview and arraylist for store chatting history */
|
| 84 |
+
private var mMessageList: ArrayList<ChatMessageModel> = ArrayList()
|
| 85 |
+
private lateinit var mAdapter: ChatAdapter
|
| 86 |
+
|
| 87 |
+
/** when a user selects image by camera or gallery,
|
| 88 |
+
* these two variables are used to save image source and name */
|
| 89 |
+
private var mSelectedImage: ByteArray? = null
|
| 90 |
+
private var mSelectedImageName: String = ""
|
| 91 |
+
|
| 92 |
+
/**
|
| 93 |
+
* mImagePickerType is
|
| 94 |
+
* 'image_uplaod' when user is going to upload image
|
| 95 |
+
* 'image_picker' when user is going to pick image for prompting
|
| 96 |
+
*/
|
| 97 |
+
private lateinit var mImagePickerWidget: ImagePickerWidget
|
| 98 |
+
private var mImagePickerType: String = ""
|
| 99 |
+
|
| 100 |
+
/** HttpClient for restful apis */
|
| 101 |
+
private lateinit var httpClient: HttpClient
|
| 102 |
+
|
| 103 |
+
/** list of help prompt commands */
|
| 104 |
+
private var mHelpPromptList: ArrayList<HelpPromptModel>? = null
|
| 105 |
+
|
| 106 |
+
/** animation variable for loading spinner */
|
| 107 |
+
private val rotate = RotateAnimation(
|
| 108 |
+
0f, 360f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f
|
| 109 |
+
)
|
| 110 |
+
|
| 111 |
+
/** room database handler for local database */
|
| 112 |
+
private lateinit var mRoomDataHandler: MyDatabase
|
| 113 |
+
|
| 114 |
+
/** status variable that checks if widget in chatting interface does exist */
|
| 115 |
+
private var mIsExistWidget: Boolean = false
|
| 116 |
+
|
| 117 |
+
/**
|
| 118 |
+
* this is invoked when users click the message icon to send sms on contact detail dialog
|
| 119 |
+
* that is shown when a user search contacts
|
| 120 |
+
*/
|
| 121 |
+
private var mSMSOnClickListener: ContactDetailItem.OnSMSClickListener? = null
|
| 122 |
+
|
| 123 |
+
override fun onCreateView(
|
| 124 |
+
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
| 125 |
+
): View {
|
| 126 |
+
rootView = inflater.inflate(R.layout.fragment_chat, container, false)
|
| 127 |
+
init()
|
| 128 |
+
return rootView
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
private fun init() {
|
| 132 |
+
initEnvironment()
|
| 133 |
+
initValues()
|
| 134 |
+
initView()
|
| 135 |
+
initDatabase()
|
| 136 |
+
|
| 137 |
+
trainImages()
|
| 138 |
+
getAllPromptCommands()
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
private fun getAllPromptCommands() {
|
| 143 |
+
showLoading(true, "Loading Help Prompt Data")
|
| 144 |
+
httpClient.getALlHelpPromptCommands()
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
private fun initEnvironment() {
|
| 149 |
+
val policy = ThreadPolicy.Builder().permitAll().build()
|
| 150 |
+
StrictMode.setThreadPolicy(policy)
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
private fun initValues() {
|
| 154 |
+
mContext = context
|
| 155 |
+
|
| 156 |
+
httpClient = HttpClient(this)
|
| 157 |
+
|
| 158 |
+
rotate.duration = 3000
|
| 159 |
+
rotate.repeatCount = Animation.INFINITE
|
| 160 |
+
rotate.interpolator = LinearInterpolator()
|
| 161 |
+
|
| 162 |
+
initSMSOnClickListener()
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
private fun initView() {
|
| 166 |
+
this.mAdapter = ChatAdapter(mMessageList, mContext!!)
|
| 167 |
+
this.mAdapter.mOnSMSClickListener = mSMSOnClickListener
|
| 168 |
+
mRvChatList = rootView.findViewById<View>(R.id.chatRecycleView) as RecyclerView
|
| 169 |
+
mRvChatList.adapter = mAdapter
|
| 170 |
+
|
| 171 |
+
val sendSMSListener = object : ChatAdapter.MessageWidgetListener {
|
| 172 |
+
override fun sentSMS(phonenumber: String, message: String) {
|
| 173 |
+
mIsExistWidget = false
|
| 174 |
+
sendSms(phonenumber, message)
|
| 175 |
+
addMessage(
|
| 176 |
+
"You sent SMS with belowing content.\n\n " + "To: $phonenumber\n " + "Message: $message",
|
| 177 |
+
false
|
| 178 |
+
)
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
override fun canceledSMS() {
|
| 182 |
+
mIsExistWidget = false
|
| 183 |
+
addMessage("You canceled SMS.", false)
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
override fun sentHelpPrompt(prompt: String) {
|
| 187 |
+
mIsExistWidget = false
|
| 188 |
+
addMessage(prompt, true)
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
override fun canceledHelpPrompt() {
|
| 192 |
+
mIsExistWidget = false
|
| 193 |
+
addMessage("You canceled help command.", false)
|
| 194 |
+
}
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
mAdapter.setMessageWidgetListener(sendSMSListener)
|
| 198 |
+
|
| 199 |
+
val linearLayoutManager = LinearLayoutManager(mContext)
|
| 200 |
+
mRvChatList.layoutManager = linearLayoutManager
|
| 201 |
+
|
| 202 |
+
this.mEtMessage = rootView.findViewById(R.id.et_message)
|
| 203 |
+
mEtMessage.setOnKeyListener { _, keyCode, keyEvent ->
|
| 204 |
+
if (keyEvent.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER) {
|
| 205 |
+
addMessage(mEtMessage.text.toString(), true)
|
| 206 |
+
}
|
| 207 |
+
return@setOnKeyListener false
|
| 208 |
+
}
|
| 209 |
+
|
| 210 |
+
rootView.findViewById<View>(R.id.btn_send_message).setOnClickListener(this)
|
| 211 |
+
rootView.findViewById<View>(R.id.btn_image_upload).setOnClickListener(this)
|
| 212 |
+
rootView.findViewById<View>(R.id.btn_image_picker).setOnClickListener(this)
|
| 213 |
+
|
| 214 |
+
mSpLoading = rootView.findViewById(R.id.sp_loading)
|
| 215 |
+
mTvLoading = rootView.findViewById(R.id.tv_loading)
|
| 216 |
+
mLlLoading = rootView.findViewById(R.id.ll_loading)
|
| 217 |
+
|
| 218 |
+
mLlLoadPhoto = rootView.findViewById(R.id.ll_load_photo)
|
| 219 |
+
mIvLoadedPhoto = rootView.findViewById(R.id.iv_load_photo)
|
| 220 |
+
mBtnCancelLoadPhoto = rootView.findViewById(R.id.btn_cancel_load_photo)
|
| 221 |
+
mBtnCancelLoadPhoto.setOnClickListener(this)
|
| 222 |
+
|
| 223 |
+
initImagePickerWidget()
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
private fun initDatabase() {
|
| 227 |
+
mRoomDataHandler = MyDatabase.getDatabase(mContext!!)
|
| 228 |
+
}
|
| 229 |
+
|
| 230 |
+
private fun initSMSOnClickListener() {
|
| 231 |
+
mSMSOnClickListener = object : ContactDetailItem.OnSMSClickListener {
|
| 232 |
+
override fun onSMSClickListener(phoneNumber: String) {
|
| 233 |
+
addMessage(
|
| 234 |
+
"SMS",
|
| 235 |
+
isMe = false,
|
| 236 |
+
isSend = false,
|
| 237 |
+
isWidget = true,
|
| 238 |
+
widgetType = MSG_WIDGET_TYPE_SMS,
|
| 239 |
+
widgetDescription = phoneNumber
|
| 240 |
+
)
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
override fun onVoiceCallListener(phoneNumber: String, toName: String) {
|
| 244 |
+
addMessage(
|
| 245 |
+
message = "You made a voice call to $toName($phoneNumber)",
|
| 246 |
+
isMe = false,
|
| 247 |
+
isSend = false
|
| 248 |
+
)
|
| 249 |
+
}
|
| 250 |
+
}
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
/**
|
| 254 |
+
* set loading spinner visible
|
| 255 |
+
*/
|
| 256 |
+
private fun setDisableActivity(enable: Boolean) {
|
| 257 |
+
runOnUIThread {
|
| 258 |
+
mEtMessage.isEnabled = enable
|
| 259 |
+
rootView.findViewById<View>(R.id.btn_send_message).isEnabled = enable
|
| 260 |
+
rootView.findViewById<View>(R.id.btn_image_upload).isEnabled = enable
|
| 261 |
+
rootView.findViewById<View>(R.id.btn_image_picker).isEnabled = enable
|
| 262 |
+
}
|
| 263 |
+
}
|
| 264 |
+
|
| 265 |
+
/**
|
| 266 |
+
* set loading spinner visible
|
| 267 |
+
*/
|
| 268 |
+
private fun showLoading(visible: Boolean, text: String = "") {
|
| 269 |
+
runOnUIThread {
|
| 270 |
+
setDisableActivity(!visible)
|
| 271 |
+
|
| 272 |
+
if (visible) {
|
| 273 |
+
mSpLoading.startAnimation(rotate)
|
| 274 |
+
mLlLoading.visibility = View.VISIBLE
|
| 275 |
+
mTvLoading.text = text
|
| 276 |
+
} else {
|
| 277 |
+
mSpLoading.clearAnimation()
|
| 278 |
+
mLlLoading.visibility = View.GONE
|
| 279 |
+
}
|
| 280 |
+
}
|
| 281 |
+
}
|
| 282 |
+
|
| 283 |
+
/**
|
| 284 |
+
* show overlay when you picked image and users can cancel to load photo if users want
|
| 285 |
+
*/
|
| 286 |
+
private fun showLoadPhotoOverlay(imageByteArray: ByteArray) {
|
| 287 |
+
mLlLoadPhoto.visibility = View.VISIBLE
|
| 288 |
+
|
| 289 |
+
mIvLoadedPhoto.setImageBitmap(
|
| 290 |
+
BitmapFactory.decodeByteArray(
|
| 291 |
+
/* data = */ imageByteArray, /* offset = */ 0, /* length = */ imageByteArray.size
|
| 292 |
+
)
|
| 293 |
+
)
|
| 294 |
+
}
|
| 295 |
+
|
| 296 |
+
/**
|
| 297 |
+
* Bind broadcast sent by MessageService
|
| 298 |
+
*/
|
| 299 |
+
private val receiver = object : BroadcastReceiver() {
|
| 300 |
+
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
|
| 301 |
+
override fun onReceive(context: Context, intent: Intent) {
|
| 302 |
+
Log.d(TAG, "Receive")
|
| 303 |
+
}
|
| 304 |
+
}
|
| 305 |
+
|
| 306 |
+
|
| 307 |
+
/**
|
| 308 |
+
* get download url from firebase imagename in response and generate download url from the imagename
|
| 309 |
+
* With getBitmapFromURL method, get bitmap of the image and set it to mSelectedImage to display it to chatlist
|
| 310 |
+
* Finally, run addmessage and insert it to chat recyclerview
|
| 311 |
+
*
|
| 312 |
+
* @param imageName the firebase store imagename
|
| 313 |
+
*/
|
| 314 |
+
private fun getImageResponse(imageName: String, imageDesc: String) {
|
| 315 |
+
if (imageName.isEmpty() && imageDesc.isNotEmpty()) {
|
| 316 |
+
addMessage(message = imageDesc, isMe = false)
|
| 317 |
+
return
|
| 318 |
+
}
|
| 319 |
+
|
| 320 |
+
showLoading(visible = true, text = LOADING_DOWNLOADING_IMAGE)
|
| 321 |
+
|
| 322 |
+
val imageNameToUpload = "images/$imageName"
|
| 323 |
+
|
| 324 |
+
val storageReference = FirebaseStorage.getInstance().getReference(imageNameToUpload)
|
| 325 |
+
storageReference.downloadUrl.addOnSuccessListener { uri ->
|
| 326 |
+
try {
|
| 327 |
+
val image = Utils.instance.getBitmapFromURL(uri.toString())
|
| 328 |
+
if (image == null) showToast("can not get bitmap from url")
|
| 329 |
+
val byteArrayOutputStream = ByteArrayOutputStream()
|
| 330 |
+
|
| 331 |
+
val isSuccess =
|
| 332 |
+
image!!.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream)
|
| 333 |
+
if (!isSuccess) {
|
| 334 |
+
showToast("Fail to compress image")
|
| 335 |
+
}
|
| 336 |
+
mSelectedImage = byteArrayOutputStream.toByteArray()
|
| 337 |
+
} catch (e: Exception) {
|
| 338 |
+
showToast("cannot get download url from firebase store")
|
| 339 |
+
e.printStackTrace()
|
| 340 |
+
}
|
| 341 |
+
|
| 342 |
+
addMessage(message = imageDesc, isMe = false)
|
| 343 |
+
showLoading(visible = false)
|
| 344 |
+
}
|
| 345 |
+
return
|
| 346 |
+
}
|
| 347 |
+
|
| 348 |
+
override fun onResume() {
|
| 349 |
+
super.onResume()
|
| 350 |
+
updateChangedUserData()
|
| 351 |
+
}
|
| 352 |
+
|
| 353 |
+
private fun updateChangedUserData() {
|
| 354 |
+
trainContacts()
|
| 355 |
+
}
|
| 356 |
+
|
| 357 |
+
/**
|
| 358 |
+
* Open Help Command Widget with analysing string command
|
| 359 |
+
*/
|
| 360 |
+
private fun openHelpCommandWidget(strCommand: String) {
|
| 361 |
+
try {
|
| 362 |
+
val command: HelpCommandModel = Utils.instance.getHelpCommandFromStr(strCommand)
|
| 363 |
+
if (command.isMainCommand()) {
|
| 364 |
+
when (command.mainCommandName) {
|
| 365 |
+
"sms" -> {
|
| 366 |
+
addMessage(
|
| 367 |
+
message = "SMS",
|
| 368 |
+
isMe = false,
|
| 369 |
+
isSend = false,
|
| 370 |
+
isWidget = true,
|
| 371 |
+
widgetType = MSG_WIDGET_TYPE_SMS
|
| 372 |
+
)
|
| 373 |
+
}
|
| 374 |
+
|
| 375 |
+
else -> {
|
| 376 |
+
mHelpPromptList!!.forEach { model ->
|
| 377 |
+
if (model.name == command.mainCommandName) {
|
| 378 |
+
addMessage(
|
| 379 |
+
"Help Prompt Command",
|
| 380 |
+
isMe = true,
|
| 381 |
+
isSend = false,
|
| 382 |
+
isWidget = true,
|
| 383 |
+
widgetType = MSG_WIDGET_TYPE_HELP_PRMOPT,
|
| 384 |
+
widgetDescription = model.toString()
|
| 385 |
+
)
|
| 386 |
+
return
|
| 387 |
+
}
|
| 388 |
+
}
|
| 389 |
+
addMessage(
|
| 390 |
+
message = "No such command name exists.", isMe = false, isSend = false
|
| 391 |
+
)
|
| 392 |
+
}
|
| 393 |
+
}
|
| 394 |
+
} else {
|
| 395 |
+
if (command.assistCommandName == "all") {
|
| 396 |
+
val usage =
|
| 397 |
+
"usage:\n" + "- help command: /help [command name]\n" + "- prompt command: /<command name>\n\n"
|
| 398 |
+
var strHelpList = "help prompt commands:"
|
| 399 |
+
mHelpPromptList!!.forEach { model ->
|
| 400 |
+
strHelpList += "\n- " + model.name
|
| 401 |
+
}
|
| 402 |
+
|
| 403 |
+
addMessage(message = usage + strHelpList, isMe = false, isSend = false)
|
| 404 |
+
} else {
|
| 405 |
+
var strHelpDesc = ""
|
| 406 |
+
mHelpPromptList!!.forEach { model ->
|
| 407 |
+
if (model.name == command.assistCommandName) {
|
| 408 |
+
var strTags = ""
|
| 409 |
+
model.tags!!.forEach { tag ->
|
| 410 |
+
strTags += " $tag"
|
| 411 |
+
}
|
| 412 |
+
strHelpDesc = "description: " + model.description + "\ntags:" + strTags
|
| 413 |
+
}
|
| 414 |
+
}
|
| 415 |
+
if (strHelpDesc.isEmpty()) addMessage(
|
| 416 |
+
message = "No such command name exists.", isMe = false, isSend = false
|
| 417 |
+
)
|
| 418 |
+
else addMessage(message = strHelpDesc, isMe = false, isSend = false)
|
| 419 |
+
}
|
| 420 |
+
}
|
| 421 |
+
} catch (e: Exception) {
|
| 422 |
+
e.printStackTrace()
|
| 423 |
+
showToast(e.message.toString())
|
| 424 |
+
}
|
| 425 |
+
}
|
| 426 |
+
|
| 427 |
+
/**
|
| 428 |
+
* add message to chat list
|
| 429 |
+
* if users picked image from camera or gallery, send post request to `sendNotification` to ask generally
|
| 430 |
+
* else do 'imageRelateness' to search image
|
| 431 |
+
*
|
| 432 |
+
* @param message message content for chat list
|
| 433 |
+
* @param isMe this identify if this is sent by langchain server or users send message to server
|
| 434 |
+
* @param isSend is boolean that checks if you send request to server
|
| 435 |
+
* @param isWidget is boolean that checks if message item has widget
|
| 436 |
+
* @param widgetType is type of Widget ex: SMS, HELP_COMMAND, etc
|
| 437 |
+
* @param widgetDescription is string that saves information for widget
|
| 438 |
+
*/
|
| 439 |
+
@SuppressLint("NotifyDataSetChanged")
|
| 440 |
+
private fun addMessage(
|
| 441 |
+
message: String,
|
| 442 |
+
isMe: Boolean,
|
| 443 |
+
isSend: Boolean = true,
|
| 444 |
+
isWidget: Boolean = false,
|
| 445 |
+
widgetType: String = "",
|
| 446 |
+
widgetDescription: String = ""
|
| 447 |
+
) {
|
| 448 |
+
if ((message.isEmpty() && mSelectedImage == null) || mIsExistWidget) return
|
| 449 |
+
if (isWidget) {
|
| 450 |
+
if (widgetType != MSG_WIDGET_TYPE_SEARCH_CONTACT) {
|
| 451 |
+
mIsExistWidget = true
|
| 452 |
+
}
|
| 453 |
+
}
|
| 454 |
+
|
| 455 |
+
val msg = ChatMessageModel()
|
| 456 |
+
msg.message = message
|
| 457 |
+
msg.isMe = isMe
|
| 458 |
+
msg.isWidget = isWidget
|
| 459 |
+
msg.widgetType = widgetType
|
| 460 |
+
msg.widgetDescription = widgetDescription
|
| 461 |
+
|
| 462 |
+
/**
|
| 463 |
+
* if users picked some image from camera or gallery, add the image to chatting message
|
| 464 |
+
*/
|
| 465 |
+
if (mSelectedImage != null) {
|
| 466 |
+
msg.image = mSelectedImage
|
| 467 |
+
mSelectedImage = null
|
| 468 |
+
}
|
| 469 |
+
|
| 470 |
+
/**
|
| 471 |
+
* if users picked some image from camera or gallery, the image upload to firebase store
|
| 472 |
+
* mSelectedImageName is uuid created uploading to firebase store
|
| 473 |
+
*/
|
| 474 |
+
if (mSelectedImageName.isNotEmpty()) {
|
| 475 |
+
msg.imageName = mSelectedImageName
|
| 476 |
+
mSelectedImageName = ""
|
| 477 |
+
}
|
| 478 |
+
|
| 479 |
+
runOnUIThread {
|
| 480 |
+
mLlLoadPhoto.visibility = View.GONE
|
| 481 |
+
|
| 482 |
+
mMessageList.add(msg)
|
| 483 |
+
mAdapter.notifyDataSetChanged()
|
| 484 |
+
mEtMessage.setText("")
|
| 485 |
+
mRvChatList.scrollTo(/* x = */ 1000, /* y = */ -1000)
|
| 486 |
+
mRvChatList.scrollToPosition(mMessageList.size - 1)
|
| 487 |
+
}
|
| 488 |
+
|
| 489 |
+
if ((message.isNotEmpty() && message.first() == '/') && isMe) {
|
| 490 |
+
openHelpCommandWidget(message)
|
| 491 |
+
return
|
| 492 |
+
}
|
| 493 |
+
|
| 494 |
+
if (isMe) {
|
| 495 |
+
if (!isSend) {
|
| 496 |
+
return
|
| 497 |
+
}
|
| 498 |
+
|
| 499 |
+
showLoading(visible = true, text = LOADING_ASKING_TO_GPT)
|
| 500 |
+
if (msg.image != null) {
|
| 501 |
+
httpClient.callImageRelatedness(msg.imageName)
|
| 502 |
+
} else {
|
| 503 |
+
httpClient.callSendNotification(message)
|
| 504 |
+
}
|
| 505 |
+
}
|
| 506 |
+
}
|
| 507 |
+
|
| 508 |
+
override fun onClick(view: View) {
|
| 509 |
+
when (view.id) {
|
| 510 |
+
R.id.btn_send_message -> {
|
| 511 |
+
addMessage(mEtMessage.text.toString(), true)
|
| 512 |
+
}
|
| 513 |
+
|
| 514 |
+
R.id.btn_image_upload -> {
|
| 515 |
+
mImagePickerType = PICKERTYPE_IMAGE_UPLOAD
|
| 516 |
+
if(rootView.findViewById<View>(R.id.ll_toolbar).visibility == View.VISIBLE)
|
| 517 |
+
hideSlidingWidget()
|
| 518 |
+
else
|
| 519 |
+
showSlidingWidget()
|
| 520 |
+
}
|
| 521 |
+
|
| 522 |
+
R.id.btn_image_picker -> {
|
| 523 |
+
mImagePickerType = PICKERTYPE_IMAGE_PICK
|
| 524 |
+
if(rootView.findViewById<View>(R.id.ll_toolbar).visibility == View.VISIBLE)
|
| 525 |
+
hideSlidingWidget()
|
| 526 |
+
else
|
| 527 |
+
showSlidingWidget()
|
| 528 |
+
}
|
| 529 |
+
|
| 530 |
+
R.id.btn_cancel_load_photo -> {
|
| 531 |
+
mLlLoadPhoto.visibility = View.GONE
|
| 532 |
+
mSelectedImageName = ""
|
| 533 |
+
mSelectedImage = null
|
| 534 |
+
}
|
| 535 |
+
}
|
| 536 |
+
}
|
| 537 |
+
|
| 538 |
+
/**
|
| 539 |
+
* open browser with url
|
| 540 |
+
* @param url to open with browser
|
| 541 |
+
*/
|
| 542 |
+
private fun openBrowser(url: String) {
|
| 543 |
+
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
| 544 |
+
startActivity(Intent.createChooser(intent, "Browse with"))
|
| 545 |
+
}
|
| 546 |
+
|
| 547 |
+
/**
|
| 548 |
+
* calls when finish picking image
|
| 549 |
+
*
|
| 550 |
+
* @param imageByteData is bytearray of picked image
|
| 551 |
+
* @param type if users are going to upload image or pick image for query
|
| 552 |
+
*/
|
| 553 |
+
private fun pickedImage(imageByteData: ByteArray, type: String) {
|
| 554 |
+
if (type == "image_upload") {
|
| 555 |
+
uploadImageToFirebaseStorage(imageByteData)
|
| 556 |
+
} else {
|
| 557 |
+
uploadSearchImage(imageByteData)
|
| 558 |
+
}
|
| 559 |
+
}
|
| 560 |
+
|
| 561 |
+
/**
|
| 562 |
+
* toast message is invoked when error happens
|
| 563 |
+
*/
|
| 564 |
+
private fun showToast(message: String) {
|
| 565 |
+
runOnUIThread {
|
| 566 |
+
Toast.makeText(
|
| 567 |
+
mContext, message, Toast.LENGTH_SHORT
|
| 568 |
+
).show()
|
| 569 |
+
}
|
| 570 |
+
}
|
| 571 |
+
|
| 572 |
+
|
| 573 |
+
/**
|
| 574 |
+
* this can show dialog with camera and gallery icon.
|
| 575 |
+
* when you click camera icon, camera application runs and users can get an image by capturing using camera.
|
| 576 |
+
* when you click gallery icon, users can select image in your storage.
|
| 577 |
+
* A picked image converts into bytearray data and upload to firebase storage.
|
| 578 |
+
*/
|
| 579 |
+
private fun initImagePickerWidget() {
|
| 580 |
+
mImagePickerWidget = ImagePickerWidget(mContext!!)
|
| 581 |
+
|
| 582 |
+
val myImplementation = object : OnPositiveButtonClickListener {
|
| 583 |
+
override fun onPositiveBtnClick(isCamera: Boolean?) {
|
| 584 |
+
if (isCamera == true) {
|
| 585 |
+
CoCo.with(activity!!).take(Utils.instance.createSDCardFile())
|
| 586 |
+
.start(object : CoCoAdapter<TakeResult>() {
|
| 587 |
+
override fun onSuccess(data: TakeResult) {
|
| 588 |
+
val byteArray: ByteArray =
|
| 589 |
+
Utils.instance.getBytesFromPath(data.savedFile!!.absolutePath)
|
| 590 |
+
pickedImage(
|
| 591 |
+
byteArray, mImagePickerType
|
| 592 |
+
)
|
| 593 |
+
}
|
| 594 |
+
|
| 595 |
+
override fun onFailed(exception: Exception) {
|
| 596 |
+
super.onFailed(exception)
|
| 597 |
+
showToast("Fail to pick image. Please try again.")
|
| 598 |
+
}
|
| 599 |
+
})
|
| 600 |
+
} else {
|
| 601 |
+
CoCo.with(activity!!).pick().range(Range.PICK_CONTENT)
|
| 602 |
+
.start(object : CoCoCallBack<PickResult> {
|
| 603 |
+
|
| 604 |
+
override fun onSuccess(data: PickResult) {
|
| 605 |
+
val byteArray: ByteArray? =
|
| 606 |
+
Utils.instance.convertImageToByte(data.originUri)
|
| 607 |
+
if (byteArray == null) showToast("can not find such a file")
|
| 608 |
+
pickedImage(byteArray!!, mImagePickerType)
|
| 609 |
+
}
|
| 610 |
+
|
| 611 |
+
override fun onFailed(exception: Exception) {
|
| 612 |
+
showToast("Fail to pick image. Please try again.")
|
| 613 |
+
}
|
| 614 |
+
})
|
| 615 |
+
}
|
| 616 |
+
}
|
| 617 |
+
}
|
| 618 |
+
|
| 619 |
+
mImagePickerWidget.setOnClickListener(myImplementation)
|
| 620 |
+
|
| 621 |
+
val slidingWidget = rootView.findViewById<LinearLayout>(R.id.ll_toolbar)
|
| 622 |
+
slidingWidget.addView(mImagePickerWidget)
|
| 623 |
+
}
|
| 624 |
+
|
| 625 |
+
private fun uploadSearchImage(imageByteArray: ByteArray) {
|
| 626 |
+
showLoading(true, LOADING_ANALYZING_IMAGE)
|
| 627 |
+
val storageRef = FirebaseStorage.getInstance().reference
|
| 628 |
+
val uuid = UUID.randomUUID()
|
| 629 |
+
val imageName = "images/${uuid}"
|
| 630 |
+
val imageRef = storageRef.child(imageName)
|
| 631 |
+
|
| 632 |
+
val uploadTask = imageRef.putBytes(imageByteArray)
|
| 633 |
+
uploadTask.addOnFailureListener {
|
| 634 |
+
showLoading(false)
|
| 635 |
+
}.addOnSuccessListener {
|
| 636 |
+
Log.d(TAG, "Success upload to firebase storage")
|
| 637 |
+
showLoading(false)
|
| 638 |
+
|
| 639 |
+
mSelectedImageName = "$uuid"
|
| 640 |
+
mSelectedImage = imageByteArray
|
| 641 |
+
|
| 642 |
+
showLoadPhotoOverlay(imageByteArray)
|
| 643 |
+
}
|
| 644 |
+
}
|
| 645 |
+
|
| 646 |
+
/**
|
| 647 |
+
* @param imageByteArray ByteArray data for image to upload to firebase storage
|
| 648 |
+
*/
|
| 649 |
+
private fun uploadImageToFirebaseStorage(imageByteArray: ByteArray) {
|
| 650 |
+
showLoading(true, LOADING_UPLOADING_IAMGE)
|
| 651 |
+
val storageRef = FirebaseStorage.getInstance().reference
|
| 652 |
+
val uuid = UUID.randomUUID()
|
| 653 |
+
val imageName = "images/${uuid}"
|
| 654 |
+
val imageRef = storageRef.child(imageName)
|
| 655 |
+
|
| 656 |
+
val uploadTask = imageRef.putBytes(imageByteArray)
|
| 657 |
+
uploadTask.addOnFailureListener {
|
| 658 |
+
showLoading(false)
|
| 659 |
+
}.addOnSuccessListener {
|
| 660 |
+
Log.d(TAG, "Success upload to firebase storage")
|
| 661 |
+
|
| 662 |
+
showLoading(false)
|
| 663 |
+
httpClient.callImageUpload("$uuid")
|
| 664 |
+
}
|
| 665 |
+
}
|
| 666 |
+
|
| 667 |
+
override fun onSuccessResult(msg: String) {
|
| 668 |
+
showLoading(false)
|
| 669 |
+
try {
|
| 670 |
+
val json = JSONObject(msg)
|
| 671 |
+
if (json.has(RESPONSE_TYPE_PROGRAM)) {
|
| 672 |
+
when (json.getString(RESPONSE_TYPE_PROGRAM)) {
|
| 673 |
+
RESPONSE_TYPE_BROWSER -> {
|
| 674 |
+
addMessage(json.getString(RESPONSE_TYPE_URL), false)
|
| 675 |
+
openBrowser(json.getString(RESPONSE_TYPE_URL))
|
| 676 |
+
return
|
| 677 |
+
}
|
| 678 |
+
|
| 679 |
+
RESPONSE_TYPE_ALERT -> {
|
| 680 |
+
MyApplication.appContext.showNotification(
|
| 681 |
+
json.getString(
|
| 682 |
+
RESPONSE_TYPE_CONTENT
|
| 683 |
+
)
|
| 684 |
+
)
|
| 685 |
+
return
|
| 686 |
+
}
|
| 687 |
+
|
| 688 |
+
RESPONSE_TYPE_MESSAGE -> {
|
| 689 |
+
addMessage(json.getString(RESPONSE_TYPE_CONTENT), false)
|
| 690 |
+
return
|
| 691 |
+
}
|
| 692 |
+
|
| 693 |
+
RESPONSE_TYPE_IMAGE -> {
|
| 694 |
+
try {
|
| 695 |
+
val imageRes = JSONObject(json.getString(RESPONSE_TYPE_CONTENT))
|
| 696 |
+
|
| 697 |
+
val imageName = if (imageRes.has("image_name")) {
|
| 698 |
+
imageRes["image_name"] as String
|
| 699 |
+
} else {
|
| 700 |
+
""
|
| 701 |
+
}
|
| 702 |
+
|
| 703 |
+
val imageDesc = if (imageRes.has("image_desc")) {
|
| 704 |
+
imageRes["image_desc"] as String
|
| 705 |
+
} else {
|
| 706 |
+
""
|
| 707 |
+
}
|
| 708 |
+
|
| 709 |
+
getImageResponse(imageName, imageDesc)
|
| 710 |
+
} catch (e: Exception) {
|
| 711 |
+
e.printStackTrace()
|
| 712 |
+
}
|
| 713 |
+
return
|
| 714 |
+
}
|
| 715 |
+
|
| 716 |
+
RESPONSE_TYPE_SMS -> {
|
| 717 |
+
addMessage(
|
| 718 |
+
json.getString(RESPONSE_TYPE_CONTENT),
|
| 719 |
+
false,
|
| 720 |
+
isSend = false,
|
| 721 |
+
isWidget = true,
|
| 722 |
+
widgetType = MSG_WIDGET_TYPE_SMS
|
| 723 |
+
)
|
| 724 |
+
}
|
| 725 |
+
|
| 726 |
+
RESPONSE_TYPE_HELP_COMMAND -> {
|
| 727 |
+
try {
|
| 728 |
+
mHelpPromptList = Utils.instance.getHelpCommandListFromJsonString(
|
| 729 |
+
json.getString(RESPONSE_TYPE_CONTENT)
|
| 730 |
+
)
|
| 731 |
+
} catch (e: java.lang.Exception) {
|
| 732 |
+
e.printStackTrace()
|
| 733 |
+
showToast("JSON Error occured")
|
| 734 |
+
}
|
| 735 |
+
}
|
| 736 |
+
|
| 737 |
+
RESPONSE_TYPE_CONTACT -> {
|
| 738 |
+
try {
|
| 739 |
+
addMessage(
|
| 740 |
+
message = "Contacts that you are looking for.",
|
| 741 |
+
isMe = false,
|
| 742 |
+
isSend = false,
|
| 743 |
+
isWidget = true,
|
| 744 |
+
widgetType = MSG_WIDGET_TYPE_SEARCH_CONTACT,
|
| 745 |
+
widgetDescription = json.getString(RESPONSE_TYPE_CONTENT)
|
| 746 |
+
)
|
| 747 |
+
} catch (e: Exception) {
|
| 748 |
+
e.printStackTrace()
|
| 749 |
+
}
|
| 750 |
+
}
|
| 751 |
+
}
|
| 752 |
+
}
|
| 753 |
+
} catch (e: JSONException) {
|
| 754 |
+
e.printStackTrace()
|
| 755 |
+
addMessage(msg, false)
|
| 756 |
+
}
|
| 757 |
+
}
|
| 758 |
+
|
| 759 |
+
override fun onFailureResult(msg: String) {
|
| 760 |
+
showLoading(false)
|
| 761 |
+
|
| 762 |
+
showToast(msg)
|
| 763 |
+
}
|
| 764 |
+
|
| 765 |
+
private fun queryImagesFromExternalStorage(contentResolver: ContentResolver): ArrayList<Uri> {
|
| 766 |
+
val listOfImageUris = ArrayList<Uri>()
|
| 767 |
+
|
| 768 |
+
val projection = arrayOf(MediaStore.Images.Media._ID)
|
| 769 |
+
|
| 770 |
+
val sortOrder = "${MediaStore.Images.Media.DATE_TAKEN} DESC"
|
| 771 |
+
|
| 772 |
+
contentResolver.query(
|
| 773 |
+
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, null, null, sortOrder
|
| 774 |
+
)?.use { cursor ->
|
| 775 |
+
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
|
| 776 |
+
while (cursor.moveToNext()) {
|
| 777 |
+
val id = cursor.getLong(idColumn)
|
| 778 |
+
val contentUri = Uri.withAppendedPath(
|
| 779 |
+
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id.toString()
|
| 780 |
+
)
|
| 781 |
+
listOfImageUris.add(contentUri)
|
| 782 |
+
}
|
| 783 |
+
}
|
| 784 |
+
return listOfImageUris
|
| 785 |
+
}
|
| 786 |
+
|
| 787 |
+
private fun trainImages() {
|
| 788 |
+
CoroutineScope(Dispatchers.IO).launch {
|
| 789 |
+
val images = queryImagesFromExternalStorage(requireContext().contentResolver)
|
| 790 |
+
val originalImages = mRoomDataHandler.imageDao().getAllImages()
|
| 791 |
+
|
| 792 |
+
images.forEach { uri ->
|
| 793 |
+
var isExist = false
|
| 794 |
+
val path = Utils.instance.getRealPathFromUri(requireContext(), uri)
|
| 795 |
+
for (i in originalImages.indices) {
|
| 796 |
+
val entity: ImageEntity = originalImages[i]
|
| 797 |
+
if (entity.path == path) {
|
| 798 |
+
isExist = true
|
| 799 |
+
break
|
| 800 |
+
}
|
| 801 |
+
}
|
| 802 |
+
if (!isExist) {
|
| 803 |
+
val byteArray = Utils.instance.getBytesFromPath(path)
|
| 804 |
+
val uuid = uploadImageToFirebaseStorage(byteArray)
|
| 805 |
+
|
| 806 |
+
if (path != null)
|
| 807 |
+
mRoomDataHandler.imageDao().insertImage(ImageEntity(0, path, "$uuid"))
|
| 808 |
+
}
|
| 809 |
+
}
|
| 810 |
+
}
|
| 811 |
+
}
|
| 812 |
+
|
| 813 |
+
private fun trainContacts() {
|
| 814 |
+
showLoading(true, "Train Contacts")
|
| 815 |
+
val contacts = Utils.instance.getContacts(mContext!!)
|
| 816 |
+
CoroutineScope(Dispatchers.Main).launch {
|
| 817 |
+
val changedContacts = Utils.instance.getChangedContacts(contacts, mRoomDataHandler)
|
| 818 |
+
httpClient.trainContacts(changedContacts)
|
| 819 |
+
}
|
| 820 |
+
}
|
| 821 |
+
|
| 822 |
+
fun sendSms(phoneNumber: String, message: String) {
|
| 823 |
+
try {
|
| 824 |
+
val smsManager = SmsManager.getDefault()
|
| 825 |
+
val parts = smsManager.divideMessage(message)
|
| 826 |
+
smsManager.sendMultipartTextMessage(/* destinationAddress = */ phoneNumber, /* scAddress = */
|
| 827 |
+
null, /* parts = */
|
| 828 |
+
parts, /* sentIntents = */
|
| 829 |
+
null, /* deliveryIntents = */
|
| 830 |
+
null)
|
| 831 |
+
} catch (e: SecurityException) {
|
| 832 |
+
e.printStackTrace()
|
| 833 |
+
} catch (e: Exception) {
|
| 834 |
+
e.printStackTrace()
|
| 835 |
+
}
|
| 836 |
+
}
|
| 837 |
+
|
| 838 |
+
@SuppressLint("UseRequireInsteadOfGet")
|
| 839 |
+
fun runOnUIThread(action: () -> Unit) {
|
| 840 |
+
requireActivity().runOnUiThread {
|
| 841 |
+
action()
|
| 842 |
+
}
|
| 843 |
+
}
|
| 844 |
+
|
| 845 |
+
private fun showSlidingWidget() {
|
| 846 |
+
val slidingWidget = rootView.findViewById<View>(R.id.ll_toolbar)
|
| 847 |
+
|
| 848 |
+
val dy = slidingWidget.measuredHeight.toFloat()
|
| 849 |
+
slidingWidget.visibility = View.VISIBLE
|
| 850 |
+
|
| 851 |
+
val anim = TranslateAnimation(0f, 0f, dy, 0f).apply {
|
| 852 |
+
duration = 150 // Set the animation duration, e.g., 300ms
|
| 853 |
+
interpolator = AccelerateDecelerateInterpolator()
|
| 854 |
+
setAnimationListener(object : Animation.AnimationListener {
|
| 855 |
+
override fun onAnimationStart(animation: Animation?) {
|
| 856 |
+
slidingWidget.visibility = View.VISIBLE
|
| 857 |
+
}
|
| 858 |
+
|
| 859 |
+
override fun onAnimationEnd(animation: Animation?) {}
|
| 860 |
+
|
| 861 |
+
override fun onAnimationRepeat(animation: Animation?) {}
|
| 862 |
+
})
|
| 863 |
+
}
|
| 864 |
+
|
| 865 |
+
slidingWidget.startAnimation(anim)
|
| 866 |
+
}
|
| 867 |
+
|
| 868 |
+
private fun hideSlidingWidget() {
|
| 869 |
+
val slidingWidget = rootView.findViewById<View>(R.id.ll_toolbar)
|
| 870 |
+
|
| 871 |
+
val anim = AlphaAnimation(1f, 0f).apply {
|
| 872 |
+
duration = 100 // Set the animation duration, e.g., 300ms
|
| 873 |
+
interpolator = AccelerateDecelerateInterpolator()
|
| 874 |
+
setAnimationListener(object : Animation.AnimationListener {
|
| 875 |
+
override fun onAnimationStart(animation: Animation?) {}
|
| 876 |
+
|
| 877 |
+
override fun onAnimationEnd(animation: Animation?) {
|
| 878 |
+
slidingWidget.visibility = View.GONE
|
| 879 |
+
}
|
| 880 |
+
|
| 881 |
+
override fun onAnimationRepeat(animation: Animation?) {}
|
| 882 |
+
})
|
| 883 |
+
}
|
| 884 |
+
|
| 885 |
+
slidingWidget.startAnimation(anim)
|
| 886 |
+
}
|
| 887 |
+
}
|
| 888 |
+
|
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/models/common/ContactModel.kt
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.matthaigh27.chatgptwrapper.models.common
|
| 2 |
+
|
| 3 |
+
class ContactModel {
|
| 4 |
+
var id: String = ""
|
| 5 |
+
var name: String = ""
|
| 6 |
+
var phoneList: ArrayList<String>? = null
|
| 7 |
+
var status: String = ""
|
| 8 |
+
|
| 9 |
+
init {
|
| 10 |
+
phoneList = ArrayList()
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
fun setData(id: String, name: String, phoneList: ArrayList<String>, status: String) {
|
| 14 |
+
this.id = id
|
| 15 |
+
this.name = name
|
| 16 |
+
this.phoneList = phoneList
|
| 17 |
+
this.status = status
|
| 18 |
+
}
|
| 19 |
+
}
|
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/models/common/HelpCommandModel.kt
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.matthaigh27.chatgptwrapper.models.common
|
| 2 |
+
|
| 3 |
+
class HelpCommandModel {
|
| 4 |
+
var mainCommandName: String = ""
|
| 5 |
+
var assistCommandName: String = ""
|
| 6 |
+
|
| 7 |
+
fun isMainCommand(): Boolean {
|
| 8 |
+
return mainCommandName != "help"
|
| 9 |
+
}
|
| 10 |
+
}
|
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/models/common/HelpPromptModel.kt
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.matthaigh27.chatgptwrapper.models.common
|
| 2 |
+
|
| 3 |
+
import com.google.gson.Gson
|
| 4 |
+
import com.google.gson.GsonBuilder
|
| 5 |
+
|
| 6 |
+
class HelpPromptModel {
|
| 7 |
+
var name: String = ""
|
| 8 |
+
var description: String = ""
|
| 9 |
+
var prompt: String = ""
|
| 10 |
+
var tags: ArrayList<String>? = null
|
| 11 |
+
|
| 12 |
+
override fun toString(): String {
|
| 13 |
+
val gson = Gson()
|
| 14 |
+
val str = gson.toJson(this)
|
| 15 |
+
return str
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
companion object {
|
| 19 |
+
fun initModelWithString(strJson: String): HelpPromptModel {
|
| 20 |
+
val gson = Gson()
|
| 21 |
+
val model: HelpPromptModel = gson.fromJson(strJson, HelpPromptModel::class.java)
|
| 22 |
+
return model
|
| 23 |
+
}
|
| 24 |
+
}
|
| 25 |
+
}
|
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/models/common/ImagePromptModel.kt
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.matthaigh27.chatgptwrapper.models.common
|
| 2 |
+
|
| 3 |
+
class ImagePromptModel {
|
| 4 |
+
var id: String = ""
|
| 5 |
+
var path: String = ""
|
| 6 |
+
|
| 7 |
+
constructor(id: String, path: String) {
|
| 8 |
+
this.id = id
|
| 9 |
+
this.path = path
|
| 10 |
+
}
|
| 11 |
+
}
|
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/models/requestmodels/RequestBodyModel.kt
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.matthaigh27.chatgptwrapper.models.requestmodels
|
| 2 |
+
|
| 3 |
+
import com.matthaigh27.chatgptwrapper.MyApplication
|
| 4 |
+
import org.json.JSONException
|
| 5 |
+
import org.json.JSONObject
|
| 6 |
+
|
| 7 |
+
class RequestBodyModel(builder: Builder) {
|
| 8 |
+
|
| 9 |
+
/** this identify request type
|
| 10 |
+
* example: it will be 'message' when users send message, 'image' when users upload image
|
| 11 |
+
*/
|
| 12 |
+
var type: String = ""
|
| 13 |
+
var token: String = ""
|
| 14 |
+
var message: String = ""
|
| 15 |
+
var imageName: String = ""
|
| 16 |
+
var uuid: String = ""
|
| 17 |
+
|
| 18 |
+
init {
|
| 19 |
+
this.token = MyApplication.appContext.getFCMToken()
|
| 20 |
+
this.uuid = MyApplication.appContext.getUUID()
|
| 21 |
+
this.message = builder.message
|
| 22 |
+
this.imageName = builder.imageName
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
@Throws(JSONException::class)
|
| 26 |
+
fun buildJsonObject(): JSONObject {
|
| 27 |
+
|
| 28 |
+
val jsonObject = JSONObject()
|
| 29 |
+
jsonObject.accumulate("type", type)
|
| 30 |
+
jsonObject.accumulate("token", token)
|
| 31 |
+
jsonObject.accumulate("message", message)
|
| 32 |
+
jsonObject.accumulate("image_name", imageName)
|
| 33 |
+
jsonObject.accumulate("uuid", uuid)
|
| 34 |
+
|
| 35 |
+
return jsonObject
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
class Builder {
|
| 39 |
+
var type: String = ""
|
| 40 |
+
var token: String = ""
|
| 41 |
+
var message: String = ""
|
| 42 |
+
var imageName: String = ""
|
| 43 |
+
var uuid: String = ""
|
| 44 |
+
|
| 45 |
+
constructor() {
|
| 46 |
+
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
constructor(request: RequestBodyModel) {
|
| 50 |
+
this.type = request.type
|
| 51 |
+
this.token = request.token
|
| 52 |
+
this.message = request.message
|
| 53 |
+
this.imageName = request.imageName
|
| 54 |
+
this.uuid = request.uuid
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
fun type(type: String): Builder {
|
| 58 |
+
this.type = type
|
| 59 |
+
return this
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
fun token(token: String): Builder {
|
| 63 |
+
this.token = token
|
| 64 |
+
return this
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
fun message(message: String): Builder {
|
| 68 |
+
this.message = message
|
| 69 |
+
return this
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
fun imageName(imageName: String): Builder {
|
| 73 |
+
this.imageName = imageName
|
| 74 |
+
return this
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
fun uuid(uuid: String): Builder {
|
| 78 |
+
this.uuid = uuid
|
| 79 |
+
return this
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
fun build(): RequestBodyModel {
|
| 83 |
+
return RequestBodyModel(this)
|
| 84 |
+
}
|
| 85 |
+
}
|
| 86 |
+
}
|
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/models/requestmodels/RequestTrainContactModel.kt
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.matthaigh27.chatgptwrapper.models.requestmodels
|
| 2 |
+
|
| 3 |
+
import com.matthaigh27.chatgptwrapper.MyApplication
|
| 4 |
+
import com.matthaigh27.chatgptwrapper.models.common.ContactModel
|
| 5 |
+
import com.matthaigh27.chatgptwrapper.utils.Utils
|
| 6 |
+
import org.json.JSONArray
|
| 7 |
+
import org.json.JSONException
|
| 8 |
+
import org.json.JSONObject
|
| 9 |
+
|
| 10 |
+
class RequestTrainContactModel(builder: Builder) {
|
| 11 |
+
|
| 12 |
+
/** this identify request type
|
| 13 |
+
* example: it will be 'message' when users send message, 'image' when users upload image
|
| 14 |
+
*/
|
| 15 |
+
var type: String = ""
|
| 16 |
+
var token: String = ""
|
| 17 |
+
var contacts = JSONArray()
|
| 18 |
+
var uuid: String = ""
|
| 19 |
+
|
| 20 |
+
init {
|
| 21 |
+
this.token = MyApplication.appContext.getFCMToken()
|
| 22 |
+
this.uuid = MyApplication.appContext.getUUID()
|
| 23 |
+
this.contacts = builder.contacts
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
@Throws(JSONException::class)
|
| 27 |
+
fun buildJsonObject(): JSONObject {
|
| 28 |
+
|
| 29 |
+
val jsonObject = JSONObject()
|
| 30 |
+
jsonObject.accumulate("type", type)
|
| 31 |
+
jsonObject.accumulate("token", token)
|
| 32 |
+
jsonObject.accumulate("contacts", contacts)
|
| 33 |
+
jsonObject.accumulate("uuid", uuid)
|
| 34 |
+
|
| 35 |
+
return jsonObject
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
class Builder {
|
| 39 |
+
var type: String = ""
|
| 40 |
+
var token: String = ""
|
| 41 |
+
var contacts = JSONArray()
|
| 42 |
+
var uuid: String = ""
|
| 43 |
+
|
| 44 |
+
constructor() {
|
| 45 |
+
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
constructor(request: RequestTrainContactModel) {
|
| 49 |
+
this.type = request.type
|
| 50 |
+
this.token = request.token
|
| 51 |
+
this.uuid = request.uuid
|
| 52 |
+
this.contacts = request.contacts
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
fun type(type: String): Builder {
|
| 56 |
+
this.type = type
|
| 57 |
+
return this
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
fun token(token: String): Builder {
|
| 61 |
+
this.token = token
|
| 62 |
+
return this
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
fun contacts(contacts: ArrayList<ContactModel>): Builder {
|
| 66 |
+
this.contacts = Utils.instance.convertContactModelToJsonArray(contacts)
|
| 67 |
+
return this
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
fun uuid(uuid: String): Builder {
|
| 71 |
+
this.uuid = uuid
|
| 72 |
+
return this
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
fun build(): RequestTrainContactModel {
|
| 76 |
+
return RequestTrainContactModel(this)
|
| 77 |
+
}
|
| 78 |
+
}
|
| 79 |
+
}
|
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/models/viewmodels/ChatMessageModel.kt
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.matthaigh27.chatgptwrapper.models.viewmodels
|
| 2 |
+
|
| 3 |
+
import androidx.lifecycle.ViewModel
|
| 4 |
+
|
| 5 |
+
/**
|
| 6 |
+
* ChatModel for Chat RecyclerView
|
| 7 |
+
*/
|
| 8 |
+
class ChatMessageModel: ViewModel() {
|
| 9 |
+
var message: String = ""
|
| 10 |
+
var isMe: Boolean = true
|
| 11 |
+
var image: ByteArray? = null
|
| 12 |
+
var imageName: String = ""
|
| 13 |
+
var visibleFeedback = false
|
| 14 |
+
var feedback = 0
|
| 15 |
+
var isWidget: Boolean = false
|
| 16 |
+
var widgetType = ""
|
| 17 |
+
var widgetDescription = ""
|
| 18 |
+
}
|
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/services/MessageService.kt
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.matthaigh27.chatgptwrapper.services
|
| 2 |
+
|
| 3 |
+
import android.content.Intent
|
| 4 |
+
import android.util.Log
|
| 5 |
+
import com.google.firebase.messaging.FirebaseMessagingService
|
| 6 |
+
import com.google.firebase.messaging.RemoteMessage
|
| 7 |
+
import com.matthaigh27.chatgptwrapper.utils.Constants.TAG
|
| 8 |
+
|
| 9 |
+
class MessageService : FirebaseMessagingService() {
|
| 10 |
+
|
| 11 |
+
/**
|
| 12 |
+
* this function is called when langchain server pushs notification into FCM
|
| 13 |
+
* @param remoteMessage is sent by FCM when langchain server pushs notification
|
| 14 |
+
*/
|
| 15 |
+
override fun onMessageReceived(remoteMessage: RemoteMessage) {
|
| 16 |
+
super.onMessageReceived(remoteMessage)
|
| 17 |
+
if (remoteMessage.notification != null) {
|
| 18 |
+
Log.d(
|
| 19 |
+
TAG, "Message Notification Body: " + remoteMessage.notification!!.body
|
| 20 |
+
)
|
| 21 |
+
|
| 22 |
+
/** intent for sending broadcast to ChatActivity */
|
| 23 |
+
val intent = Intent()
|
| 24 |
+
intent.action = "android.intent.action.MAIN"
|
| 25 |
+
intent.putExtra("message", remoteMessage.notification!!.body)
|
| 26 |
+
|
| 27 |
+
/** send broadcast to ChatActivity
|
| 28 |
+
* So ChatActivity can receive remoteMessage from this service */
|
| 29 |
+
sendBroadcast(intent)
|
| 30 |
+
}
|
| 31 |
+
}
|
| 32 |
+
}
|
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/services/api/HttpClient.kt
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.matthaigh27.chatgptwrapper.services.api
|
| 2 |
+
|
| 3 |
+
import android.util.Log
|
| 4 |
+
import com.matthaigh27.chatgptwrapper.BuildConfig
|
| 5 |
+
import com.matthaigh27.chatgptwrapper.models.common.ContactModel
|
| 6 |
+
import com.matthaigh27.chatgptwrapper.models.requestmodels.RequestBodyModel
|
| 7 |
+
import com.matthaigh27.chatgptwrapper.models.requestmodels.RequestTrainContactModel
|
| 8 |
+
import com.matthaigh27.chatgptwrapper.utils.Constants
|
| 9 |
+
import com.matthaigh27.chatgptwrapper.utils.Constants.GET
|
| 10 |
+
import com.matthaigh27.chatgptwrapper.utils.Constants.POST
|
| 11 |
+
import com.matthaigh27.chatgptwrapper.utils.ReqType
|
| 12 |
+
import okhttp3.Call
|
| 13 |
+
import okhttp3.Callback
|
| 14 |
+
import okhttp3.OkHttpClient
|
| 15 |
+
import okhttp3.Request
|
| 16 |
+
import okhttp3.RequestBody
|
| 17 |
+
import okhttp3.Response
|
| 18 |
+
import org.json.JSONException
|
| 19 |
+
import org.json.JSONObject
|
| 20 |
+
import java.io.IOException
|
| 21 |
+
import java.util.concurrent.TimeUnit
|
| 22 |
+
|
| 23 |
+
class HttpClient {
|
| 24 |
+
/* Server URL and Api Endpoints */
|
| 25 |
+
val SERVER_URL = BuildConfig.BASE_URL
|
| 26 |
+
val SEND_NOTIFICATION_URL = SERVER_URL + "sendNotification"
|
| 27 |
+
val IMAGE_RELATEDNESS = SERVER_URL + "image_relatedness"
|
| 28 |
+
val UPLOAD_IMAGE = SERVER_URL + "uploadImage"
|
| 29 |
+
val GET_ALL_HELP_COMMANDS = SERVER_URL + "commands"
|
| 30 |
+
val TRAIN_CONTACTS = SERVER_URL + "train/contacts"
|
| 31 |
+
|
| 32 |
+
var mCallback: HttpRisingInterface
|
| 33 |
+
|
| 34 |
+
constructor(callback: HttpRisingInterface) {
|
| 35 |
+
mCallback = callback
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
private fun sendOkHttpRequest(postBody: String, postUrl: String, method: String) {
|
| 39 |
+
val body: RequestBody = RequestBody.create(Constants.JSON, postBody)
|
| 40 |
+
|
| 41 |
+
/**
|
| 42 |
+
* set okhttpclient timeout to 120s
|
| 43 |
+
*/
|
| 44 |
+
var request: Request? = null
|
| 45 |
+
if (method == POST) request = Request.Builder().url(postUrl).post(body).build()
|
| 46 |
+
else request = Request.Builder().url(postUrl).get().build()
|
| 47 |
+
|
| 48 |
+
val client =
|
| 49 |
+
OkHttpClient.Builder().connectTimeout(Constants.CUSTOM_TIMEOUT, TimeUnit.SECONDS)
|
| 50 |
+
.readTimeout(Constants.CUSTOM_TIMEOUT, TimeUnit.SECONDS)
|
| 51 |
+
.writeTimeout(Constants.CUSTOM_TIMEOUT, TimeUnit.SECONDS).build()
|
| 52 |
+
|
| 53 |
+
client.newCall(request).enqueue(object : Callback {
|
| 54 |
+
override fun onFailure(call: Call, e: IOException) {
|
| 55 |
+
/**
|
| 56 |
+
* Handle failure
|
| 57 |
+
*/
|
| 58 |
+
e.printStackTrace()
|
| 59 |
+
|
| 60 |
+
mCallback.onFailureResult("Fail to send request to server. Please ask again.")
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
override fun onResponse(call: Call, response: Response) {
|
| 64 |
+
val myResponse = response.body()!!.string()
|
| 65 |
+
Log.d(Constants.TAG, myResponse)
|
| 66 |
+
|
| 67 |
+
try {
|
| 68 |
+
val json = JSONObject(myResponse)["result"].toString()
|
| 69 |
+
mCallback.onSuccessResult(json)
|
| 70 |
+
} catch (e: JSONException) {
|
| 71 |
+
mCallback.onFailureResult(myResponse)
|
| 72 |
+
e.printStackTrace()
|
| 73 |
+
}
|
| 74 |
+
}
|
| 75 |
+
})
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
/* call sendNotification */
|
| 79 |
+
fun callSendNotification(message: String) {
|
| 80 |
+
sendOkHttpRequest(
|
| 81 |
+
RequestBodyModel.Builder().message(message).type(ReqType.instance.MESSAGE).build()
|
| 82 |
+
.buildJsonObject().toString(), SEND_NOTIFICATION_URL, POST
|
| 83 |
+
)
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
/* call image_relatedness */
|
| 87 |
+
fun callImageRelatedness(imageName: String) {
|
| 88 |
+
sendOkHttpRequest(
|
| 89 |
+
RequestBodyModel.Builder().imageName(imageName).type(ReqType.instance.MESSAGE).build()
|
| 90 |
+
.buildJsonObject().toString(), IMAGE_RELATEDNESS, POST
|
| 91 |
+
)
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
/* call image_upload */
|
| 95 |
+
fun callImageUpload(imageName: String) {
|
| 96 |
+
sendOkHttpRequest(
|
| 97 |
+
RequestBodyModel.Builder().imageName(imageName).type(ReqType.instance.IMAGE_UPLOAD)
|
| 98 |
+
.build().buildJsonObject().toString(), UPLOAD_IMAGE, POST
|
| 99 |
+
)
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
fun getALlHelpPromptCommands() {
|
| 103 |
+
sendOkHttpRequest(
|
| 104 |
+
RequestBodyModel.Builder().build().buildJsonObject().toString(),
|
| 105 |
+
GET_ALL_HELP_COMMANDS,
|
| 106 |
+
GET
|
| 107 |
+
)
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
fun trainContacts(contacts: ArrayList<ContactModel>) {
|
| 111 |
+
sendOkHttpRequest(
|
| 112 |
+
RequestTrainContactModel.Builder().contacts(contacts).build().buildJsonObject().toString(),
|
| 113 |
+
TRAIN_CONTACTS,
|
| 114 |
+
POST
|
| 115 |
+
)
|
| 116 |
+
}
|
| 117 |
+
}
|
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/services/api/HttpRisingInterface.kt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.matthaigh27.chatgptwrapper.services.api;
|
| 2 |
+
|
| 3 |
+
interface HttpRisingInterface {
|
| 4 |
+
fun onSuccessResult(msg: String)
|
| 5 |
+
|
| 6 |
+
fun onFailureResult(msg: String)
|
| 7 |
+
}
|
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/utils/Constants.java
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.matthaigh27.chatgptwrapper.utils;
|
| 2 |
+
|
| 3 |
+
import okhttp3.MediaType;
|
| 4 |
+
|
| 5 |
+
/**
|
| 6 |
+
* const variables
|
| 7 |
+
*/
|
| 8 |
+
public class Constants {
|
| 9 |
+
/**
|
| 10 |
+
* app names
|
| 11 |
+
*/
|
| 12 |
+
public static String RESPONSE_TYPE_BROWSER = "browser";
|
| 13 |
+
public static String RESPONSE_TYPE_ALERT = "alert";
|
| 14 |
+
public static String RESPONSE_TYPE_MESSAGE = "message";
|
| 15 |
+
public static String RESPONSE_TYPE_PROGRAM = "program";
|
| 16 |
+
public static String RESPONSE_TYPE_CONTENT = "content";
|
| 17 |
+
public static String RESPONSE_TYPE_URL = "url";
|
| 18 |
+
public static String RESPONSE_TYPE_IMAGE = "image";
|
| 19 |
+
public static String RESPONSE_TYPE_HELP_COMMAND = "help_command";
|
| 20 |
+
public static String RESPONSE_TYPE_SMS = "sms";
|
| 21 |
+
public static String RESPONSE_TYPE_CONTACT = "contact";
|
| 22 |
+
|
| 23 |
+
/**
|
| 24 |
+
* message widget type
|
| 25 |
+
*/
|
| 26 |
+
public static String MSG_WIDGET_TYPE_SMS = "SMS";
|
| 27 |
+
public static String MSG_WIDGET_TYPE_HELP_PRMOPT = "HELP_PROMPT";
|
| 28 |
+
public static String MSG_WIDGET_TYPE_SEARCH_CONTACT = "SEARCH_CONTACT";
|
| 29 |
+
|
| 30 |
+
/**
|
| 31 |
+
* okhttp server url
|
| 32 |
+
*/
|
| 33 |
+
|
| 34 |
+
public static String SERVER_URL = "https://smartphone.herokuapp.com/";
|
| 35 |
+
public static long CUSTOM_TIMEOUT = 120;
|
| 36 |
+
|
| 37 |
+
/**
|
| 38 |
+
* ImagePickerType
|
| 39 |
+
*/
|
| 40 |
+
public static String PICKERTYPE_IMAGE_UPLOAD = "image_upload";
|
| 41 |
+
public static String PICKERTYPE_IMAGE_PICK = "image_picker";
|
| 42 |
+
|
| 43 |
+
/**
|
| 44 |
+
* for send OkHttp3Request with json format
|
| 45 |
+
*/
|
| 46 |
+
public static MediaType JSON = MediaType.parse("application/json; charset=utf-8");
|
| 47 |
+
public static String POST = "post";
|
| 48 |
+
public static String GET = "get";
|
| 49 |
+
|
| 50 |
+
public static String TAG = "risingandroid";
|
| 51 |
+
|
| 52 |
+
public static String LOADING_ASKING_TO_GPT = "Asking To GPT";
|
| 53 |
+
public static String LOADING_UPLOADING_IAMGE = "Uploading Image";
|
| 54 |
+
public static String LOADING_ANALYZING_IMAGE = "Analyzing Image";
|
| 55 |
+
public static String LOADING_DOWNLOADING_IMAGE = "Downloading Image";
|
| 56 |
+
|
| 57 |
+
public static String HELP_COMMAND_ERROR_NO_MAIN = "no main command";
|
| 58 |
+
public static String HELP_COMMAND_ERROR_NO_INVALID_FORMAT = "Invalid Command Format";
|
| 59 |
+
public static String HELP_COMMAND = "help";
|
| 60 |
+
public static String HELP_COMMAND_ALL = "all";
|
| 61 |
+
|
| 62 |
+
public static String ERROR_MSG_JSON = "JSON Invalid Format";
|
| 63 |
+
}
|
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/utils/ImageHelper.java
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.matthaigh27.chatgptwrapper.utils;
|
| 2 |
+
|
| 3 |
+
import android.graphics.Bitmap;
|
| 4 |
+
import android.graphics.Canvas;
|
| 5 |
+
import android.graphics.Paint;
|
| 6 |
+
import android.graphics.PorterDuffXfermode;
|
| 7 |
+
import android.graphics.Rect;
|
| 8 |
+
import android.graphics.RectF;
|
| 9 |
+
import android.graphics.Bitmap.Config;
|
| 10 |
+
import android.graphics.PorterDuff.Mode;
|
| 11 |
+
|
| 12 |
+
/**
|
| 13 |
+
* static functions for image process
|
| 14 |
+
*/
|
| 15 |
+
public class ImageHelper {
|
| 16 |
+
|
| 17 |
+
/**
|
| 18 |
+
* this function convert original bitmap to rounded bitmap
|
| 19 |
+
*
|
| 20 |
+
* @param bitmap bitmap to make conner round
|
| 21 |
+
* @param pixels conner round pixel size
|
| 22 |
+
* @return rounded bitmap file conner-rounded with given pixels
|
| 23 |
+
*/
|
| 24 |
+
public static Bitmap getRoundedCornerBitmap(Bitmap bitmap, int pixels) {
|
| 25 |
+
Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Config.ARGB_8888);
|
| 26 |
+
Canvas canvas = new Canvas(output);
|
| 27 |
+
|
| 28 |
+
final int color = 0xff424242;
|
| 29 |
+
final Paint paint = new Paint();
|
| 30 |
+
final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
|
| 31 |
+
final RectF rectF = new RectF(rect);
|
| 32 |
+
final float roundPx = pixels;
|
| 33 |
+
|
| 34 |
+
paint.setAntiAlias(true);
|
| 35 |
+
canvas.drawARGB(0, 0, 0, 0);
|
| 36 |
+
paint.setColor(color);
|
| 37 |
+
canvas.drawRoundRect(rectF, roundPx, roundPx, paint);
|
| 38 |
+
|
| 39 |
+
paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
|
| 40 |
+
canvas.drawBitmap(bitmap, rect, rect, paint);
|
| 41 |
+
|
| 42 |
+
return output;
|
| 43 |
+
}
|
| 44 |
+
}
|
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/utils/ReqType.kt
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.matthaigh27.chatgptwrapper.utils
|
| 2 |
+
|
| 3 |
+
class ReqType {
|
| 4 |
+
val MESSAGE = "message"
|
| 5 |
+
val IMAGE_UPLOAD = "image_upload"
|
| 6 |
+
|
| 7 |
+
companion object {
|
| 8 |
+
var instance: ReqType = ReqType()
|
| 9 |
+
}
|
| 10 |
+
}
|
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/utils/Utils.kt
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.matthaigh27.chatgptwrapper.utils
|
| 2 |
+
|
| 3 |
+
import android.annotation.SuppressLint
|
| 4 |
+
import android.content.ContentResolver
|
| 5 |
+
import android.content.ContentUris
|
| 6 |
+
import android.content.Context
|
| 7 |
+
import android.database.Cursor
|
| 8 |
+
import android.graphics.Bitmap
|
| 9 |
+
import android.graphics.BitmapFactory
|
| 10 |
+
import android.net.Uri
|
| 11 |
+
import android.os.Environment
|
| 12 |
+
import android.provider.ContactsContract
|
| 13 |
+
import android.provider.MediaStore
|
| 14 |
+
import android.widget.ImageView
|
| 15 |
+
import androidx.room.RoomDatabase
|
| 16 |
+
import com.bumptech.glide.Glide
|
| 17 |
+
import com.matthaigh27.chatgptwrapper.MyApplication
|
| 18 |
+
import com.matthaigh27.chatgptwrapper.R
|
| 19 |
+
import com.matthaigh27.chatgptwrapper.database.MyDatabase
|
| 20 |
+
import com.matthaigh27.chatgptwrapper.database.entity.ContactEntity
|
| 21 |
+
import com.matthaigh27.chatgptwrapper.models.common.ContactModel
|
| 22 |
+
import com.matthaigh27.chatgptwrapper.models.common.HelpCommandModel
|
| 23 |
+
import com.matthaigh27.chatgptwrapper.models.common.HelpPromptModel
|
| 24 |
+
import com.matthaigh27.chatgptwrapper.services.api.HttpClient
|
| 25 |
+
import com.matthaigh27.chatgptwrapper.utils.Constants.*
|
| 26 |
+
import kotlinx.coroutines.CoroutineScope
|
| 27 |
+
import kotlinx.coroutines.Dispatchers
|
| 28 |
+
import kotlinx.coroutines.async
|
| 29 |
+
import kotlinx.coroutines.launch
|
| 30 |
+
import org.json.JSONArray
|
| 31 |
+
import org.json.JSONException
|
| 32 |
+
import org.json.JSONObject
|
| 33 |
+
import java.io.ByteArrayOutputStream
|
| 34 |
+
import java.io.File
|
| 35 |
+
import java.io.FileInputStream
|
| 36 |
+
import java.io.FileNotFoundException
|
| 37 |
+
import java.io.IOException
|
| 38 |
+
import java.io.InputStream
|
| 39 |
+
import java.net.HttpURLConnection
|
| 40 |
+
import java.net.URL
|
| 41 |
+
import java.text.SimpleDateFormat
|
| 42 |
+
import java.util.Date
|
| 43 |
+
import java.util.Locale
|
| 44 |
+
|
| 45 |
+
class Utils {
|
| 46 |
+
/**
|
| 47 |
+
* set bitmap from download url generated by firebase store
|
| 48 |
+
*
|
| 49 |
+
* @param src download url of image
|
| 50 |
+
* @return bitmap of the image
|
| 51 |
+
*/
|
| 52 |
+
fun getBitmapFromURL(src: String?): Bitmap? {
|
| 53 |
+
return try {
|
| 54 |
+
val url = URL(src)
|
| 55 |
+
val connection: HttpURLConnection = url.openConnection() as HttpURLConnection
|
| 56 |
+
connection.doInput = true
|
| 57 |
+
connection.connect()
|
| 58 |
+
val input: InputStream = connection.inputStream
|
| 59 |
+
BitmapFactory.decodeStream(input)
|
| 60 |
+
} catch (e: Exception) {
|
| 61 |
+
e.printStackTrace()
|
| 62 |
+
null
|
| 63 |
+
}
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
/**
|
| 67 |
+
* @param uri uri for converting to ByteArray
|
| 68 |
+
* @return ByteArray data converted from image uri
|
| 69 |
+
*/
|
| 70 |
+
fun convertImageToByte(uri: Uri?): ByteArray? {
|
| 71 |
+
var data: ByteArray? = null
|
| 72 |
+
try {
|
| 73 |
+
val cr = MyApplication.appContext.contentResolver
|
| 74 |
+
val inputStream: InputStream? = cr.openInputStream(uri!!)
|
| 75 |
+
val bitmap = BitmapFactory.decodeStream(inputStream)
|
| 76 |
+
val baos = ByteArrayOutputStream()
|
| 77 |
+
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos)
|
| 78 |
+
data = baos.toByteArray()
|
| 79 |
+
} catch (e: FileNotFoundException) {
|
| 80 |
+
e.printStackTrace()
|
| 81 |
+
}
|
| 82 |
+
return data
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
/**
|
| 86 |
+
* @param path local path for converting to ByteArray
|
| 87 |
+
* @return ByteArray data converted from image local path
|
| 88 |
+
*/
|
| 89 |
+
@Suppress("UNREACHABLE_CODE")
|
| 90 |
+
fun getBytesFromPath(path: String?): ByteArray {
|
| 91 |
+
var byteArray: ByteArray? = null
|
| 92 |
+
try {
|
| 93 |
+
val stream = FileInputStream(path)
|
| 94 |
+
byteArray = stream.readBytes()
|
| 95 |
+
stream.close()
|
| 96 |
+
return byteArray
|
| 97 |
+
} catch (e: IOException) {
|
| 98 |
+
throw Exception(e)
|
| 99 |
+
}
|
| 100 |
+
return byteArray
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
/**
|
| 104 |
+
* this creats image file with current datetime captured by camera
|
| 105 |
+
*/
|
| 106 |
+
fun createSDCardFile(): File {
|
| 107 |
+
val timeStamp: String =
|
| 108 |
+
SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
|
| 109 |
+
val imageFileName = "IMG_" + timeStamp + "_"
|
| 110 |
+
val storageDir: File =
|
| 111 |
+
MyApplication.appContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES)!!
|
| 112 |
+
return File.createTempFile(
|
| 113 |
+
imageFileName, /* prefix */
|
| 114 |
+
".jpg", /* suffix */
|
| 115 |
+
storageDir /* directory */
|
| 116 |
+
)
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
/**
|
| 120 |
+
* when users input command starting with '/'
|
| 121 |
+
*
|
| 122 |
+
* @param strCommand is string with '/' and '-', ex: /browser -d
|
| 123 |
+
* @return command model with main ommand and assistance command
|
| 124 |
+
*/
|
| 125 |
+
fun getHelpCommandFromStr(strCommand: String): HelpCommandModel {
|
| 126 |
+
val commandModel = HelpCommandModel()
|
| 127 |
+
if (strCommand == "/$HELP_COMMAND") {
|
| 128 |
+
commandModel.mainCommandName = HELP_COMMAND
|
| 129 |
+
commandModel.assistCommandName = HELP_COMMAND_ALL
|
| 130 |
+
return commandModel
|
| 131 |
+
}
|
| 132 |
+
try {
|
| 133 |
+
if (strCommand.startsWith("/$HELP_COMMAND") || strCommand.startsWith("!$HELP_COMMAND")) {
|
| 134 |
+
val words = strCommand.split("\\s".toRegex()).toTypedArray()
|
| 135 |
+
if (words.size != 2) {
|
| 136 |
+
throw Exception(HELP_COMMAND_ERROR_NO_MAIN)
|
| 137 |
+
}
|
| 138 |
+
commandModel.mainCommandName = words[0].substring(1, words[0].length)
|
| 139 |
+
commandModel.assistCommandName = words[1]
|
| 140 |
+
if (commandModel.mainCommandName == "" || commandModel.assistCommandName == "") {
|
| 141 |
+
throw Exception(HELP_COMMAND_ERROR_NO_MAIN)
|
| 142 |
+
}
|
| 143 |
+
} else {
|
| 144 |
+
commandModel.mainCommandName = strCommand.substring(1, strCommand.length)
|
| 145 |
+
}
|
| 146 |
+
} catch (e: Exception) {
|
| 147 |
+
throw Exception(HELP_COMMAND_ERROR_NO_INVALID_FORMAT)
|
| 148 |
+
}
|
| 149 |
+
return commandModel
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
fun getHelpCommandListFromJsonString(jsonStrCommandList: String): ArrayList<HelpPromptModel> {
|
| 153 |
+
val commandlist = ArrayList<HelpPromptModel>()
|
| 154 |
+
try {
|
| 155 |
+
val helpCommandStrList = JSONArray(jsonStrCommandList)
|
| 156 |
+
for (i in 0 until helpCommandStrList.length()) {
|
| 157 |
+
val helpCommand = JSONObject(helpCommandStrList[i].toString())
|
| 158 |
+
|
| 159 |
+
val helpPromptModel = HelpPromptModel()
|
| 160 |
+
helpPromptModel.name = helpCommand.getString("name")
|
| 161 |
+
helpPromptModel.description = helpCommand.getString("description")
|
| 162 |
+
helpPromptModel.prompt = helpCommand.getString("prompt")
|
| 163 |
+
|
| 164 |
+
helpPromptModel.tags = ArrayList()
|
| 165 |
+
val jsonArrayTags = helpCommand.getJSONArray("tags")
|
| 166 |
+
for (j in 0 until jsonArrayTags.length()) {
|
| 167 |
+
helpPromptModel.tags!!.add(jsonArrayTags[j].toString())
|
| 168 |
+
}
|
| 169 |
+
commandlist.add(helpPromptModel)
|
| 170 |
+
}
|
| 171 |
+
} catch (e: JSONException) {
|
| 172 |
+
e.printStackTrace()
|
| 173 |
+
throw Exception(ERROR_MSG_JSON)
|
| 174 |
+
}
|
| 175 |
+
return commandlist
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
@SuppressLint("Range")
|
| 179 |
+
fun getContacts(context: Context): ArrayList<ContactModel> {
|
| 180 |
+
val resolver: ContentResolver = context.contentResolver;
|
| 181 |
+
val cursor = resolver.query(
|
| 182 |
+
ContactsContract.Contacts.CONTENT_URI, null, null, null, null
|
| 183 |
+
)
|
| 184 |
+
|
| 185 |
+
val contacts = ArrayList<ContactModel>()
|
| 186 |
+
if (cursor!!.count > 0) {
|
| 187 |
+
while (cursor.moveToNext()) {
|
| 188 |
+
val id = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID))
|
| 189 |
+
|
| 190 |
+
val name =
|
| 191 |
+
cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME))
|
| 192 |
+
val phoneNumber = (cursor.getString(
|
| 193 |
+
cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER)
|
| 194 |
+
)).toInt()
|
| 195 |
+
|
| 196 |
+
val contact = ContactModel()
|
| 197 |
+
contact.id = id
|
| 198 |
+
contact.name = name
|
| 199 |
+
|
| 200 |
+
if (phoneNumber > 0) {
|
| 201 |
+
val cursorPhone = context.contentResolver.query(
|
| 202 |
+
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
|
| 203 |
+
null,
|
| 204 |
+
ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "=?",
|
| 205 |
+
arrayOf(id),
|
| 206 |
+
null
|
| 207 |
+
)
|
| 208 |
+
|
| 209 |
+
if (cursorPhone!!.count > 0) {
|
| 210 |
+
while (cursorPhone.moveToNext()) {
|
| 211 |
+
val phoneNumValue = cursorPhone.getString(
|
| 212 |
+
cursorPhone.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)
|
| 213 |
+
)
|
| 214 |
+
contact.phoneList!!.add(phoneNumValue)
|
| 215 |
+
}
|
| 216 |
+
}
|
| 217 |
+
cursorPhone.close()
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
contacts.add(contact)
|
| 221 |
+
}
|
| 222 |
+
}
|
| 223 |
+
cursor.close()
|
| 224 |
+
return contacts
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
suspend fun getChangedContacts(
|
| 228 |
+
contacts: ArrayList<ContactModel>,
|
| 229 |
+
roomDatabaseHandler: MyDatabase
|
| 230 |
+
): ArrayList<ContactModel> {
|
| 231 |
+
return CoroutineScope(Dispatchers.IO).async {
|
| 232 |
+
val originalContacts = roomDatabaseHandler.contactDao().getAllContacts()
|
| 233 |
+
val changedContactList = ArrayList<ContactModel>()
|
| 234 |
+
for (i in originalContacts.indices) {
|
| 235 |
+
var isExist = false
|
| 236 |
+
contacts.forEach { contact ->
|
| 237 |
+
if (originalContacts[i].id == contact.id) {
|
| 238 |
+
if (originalContacts[i].name != contact.name ||
|
| 239 |
+
originalContacts[i].phoneNumber != contact.phoneList.toString()
|
| 240 |
+
) {
|
| 241 |
+
contact.status = "updated"
|
| 242 |
+
changedContactList.add(contact)
|
| 243 |
+
|
| 244 |
+
try {
|
| 245 |
+
roomDatabaseHandler.contactDao().updateContact(
|
| 246 |
+
ContactEntity(
|
| 247 |
+
contact.id,
|
| 248 |
+
contact.name,
|
| 249 |
+
contact.phoneList.toString()
|
| 250 |
+
)
|
| 251 |
+
)
|
| 252 |
+
} catch (e: Exception) {
|
| 253 |
+
e.printStackTrace()
|
| 254 |
+
}
|
| 255 |
+
} else {
|
| 256 |
+
contact.status = "nothing"
|
| 257 |
+
}
|
| 258 |
+
isExist = true
|
| 259 |
+
return@forEach
|
| 260 |
+
}
|
| 261 |
+
}
|
| 262 |
+
if (!isExist) {
|
| 263 |
+
val deletedContacts = ContactModel()
|
| 264 |
+
deletedContacts.id = originalContacts[i].id
|
| 265 |
+
deletedContacts.status = "deleted"
|
| 266 |
+
changedContactList.add(deletedContacts)
|
| 267 |
+
|
| 268 |
+
try {
|
| 269 |
+
roomDatabaseHandler.contactDao().deleteContact(
|
| 270 |
+
ContactEntity(
|
| 271 |
+
deletedContacts.id,
|
| 272 |
+
deletedContacts.name,
|
| 273 |
+
deletedContacts.phoneList.toString()
|
| 274 |
+
)
|
| 275 |
+
)
|
| 276 |
+
} catch (e: Exception) {
|
| 277 |
+
e.printStackTrace()
|
| 278 |
+
}
|
| 279 |
+
}
|
| 280 |
+
}
|
| 281 |
+
contacts.forEach { contact ->
|
| 282 |
+
if (contact.status.isEmpty()) {
|
| 283 |
+
contact.status = "created"
|
| 284 |
+
changedContactList.add(contact)
|
| 285 |
+
try {
|
| 286 |
+
roomDatabaseHandler.contactDao().insertContact(
|
| 287 |
+
ContactEntity(
|
| 288 |
+
contact.id,
|
| 289 |
+
contact.name,
|
| 290 |
+
contact.phoneList.toString()
|
| 291 |
+
)
|
| 292 |
+
)
|
| 293 |
+
} catch (e: Exception) {
|
| 294 |
+
e.printStackTrace()
|
| 295 |
+
}
|
| 296 |
+
}
|
| 297 |
+
}
|
| 298 |
+
changedContactList
|
| 299 |
+
}.await()
|
| 300 |
+
}
|
| 301 |
+
|
| 302 |
+
fun getContactModelById(contactId: String, contacts: ArrayList<ContactModel>): ContactModel {
|
| 303 |
+
var contactModel = ContactModel()
|
| 304 |
+
contacts.forEach { contact ->
|
| 305 |
+
if (contactId == contact.id) {
|
| 306 |
+
return contact
|
| 307 |
+
}
|
| 308 |
+
}
|
| 309 |
+
return contactModel
|
| 310 |
+
}
|
| 311 |
+
|
| 312 |
+
fun setContactAvatar(contactId: Long, context: Context, imageView: ImageView) {
|
| 313 |
+
val uri = ContentUris.withAppendedId(
|
| 314 |
+
ContactsContract.Contacts.CONTENT_URI, contactId
|
| 315 |
+
)
|
| 316 |
+
|
| 317 |
+
Glide.with(context)
|
| 318 |
+
.load(uri)
|
| 319 |
+
.placeholder(R.drawable.default_avatar) // Set placeholder image
|
| 320 |
+
.error(R.drawable.default_avatar) // Set error image
|
| 321 |
+
.fallback(R.drawable.default_avatar) // Set fallback image
|
| 322 |
+
.into(imageView)
|
| 323 |
+
}
|
| 324 |
+
|
| 325 |
+
fun convertContactModelToJsonArray(contacts: ArrayList<ContactModel>): JSONArray {
|
| 326 |
+
var jsonContacts = JSONArray()
|
| 327 |
+
contacts.forEach { contact ->
|
| 328 |
+
val jsonObjectContact = JSONObject()
|
| 329 |
+
jsonObjectContact.put("contactId", contact.id)
|
| 330 |
+
jsonObjectContact.put("displayName", contact.name)
|
| 331 |
+
jsonObjectContact.put("phoneNumbers", contact.phoneList)
|
| 332 |
+
jsonObjectContact.put("status", contact.status)
|
| 333 |
+
|
| 334 |
+
jsonContacts.put(jsonObjectContact)
|
| 335 |
+
}
|
| 336 |
+
return jsonContacts
|
| 337 |
+
}
|
| 338 |
+
|
| 339 |
+
fun getRealPathFromUri(context: Context, contentUri: Uri?): String? {
|
| 340 |
+
var cursor: Cursor? = null
|
| 341 |
+
return try {
|
| 342 |
+
val proj = arrayOf(MediaStore.Images.Media.DATA)
|
| 343 |
+
cursor = context.contentResolver.query(contentUri!!, proj, null, null, null)
|
| 344 |
+
val column_index: Int = cursor!!.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
|
| 345 |
+
cursor.moveToFirst()
|
| 346 |
+
cursor.getString(column_index)
|
| 347 |
+
} finally {
|
| 348 |
+
cursor?.close()
|
| 349 |
+
}
|
| 350 |
+
}
|
| 351 |
+
|
| 352 |
+
|
| 353 |
+
companion object {
|
| 354 |
+
var instance: Utils = Utils()
|
| 355 |
+
}
|
| 356 |
+
}
|
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/widgets/ContactDetailItem.kt
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.matthaigh27.chatgptwrapper.widgets
|
| 2 |
+
|
| 3 |
+
import android.content.ContentUris
|
| 4 |
+
import android.content.Context
|
| 5 |
+
import android.content.Intent
|
| 6 |
+
import android.content.pm.PackageManager
|
| 7 |
+
import android.net.Uri
|
| 8 |
+
import android.provider.ContactsContract
|
| 9 |
+
import android.telecom.VideoProfile
|
| 10 |
+
import android.util.AttributeSet
|
| 11 |
+
import android.view.LayoutInflater
|
| 12 |
+
import android.view.View
|
| 13 |
+
import android.widget.ImageView
|
| 14 |
+
import android.widget.TextView
|
| 15 |
+
import androidx.constraintlayout.widget.ConstraintLayout
|
| 16 |
+
import com.matthaigh27.chatgptwrapper.R
|
| 17 |
+
import com.matthaigh27.chatgptwrapper.models.common.ContactModel
|
| 18 |
+
|
| 19 |
+
class ContactDetailItem(
|
| 20 |
+
context: Context, attrs: AttributeSet? = null
|
| 21 |
+
) : ConstraintLayout(context, attrs), View.OnClickListener {
|
| 22 |
+
private lateinit var mTvPhoneNumber: TextView
|
| 23 |
+
private lateinit var mTvPhoneType: TextView
|
| 24 |
+
private var mPhoneNumber: String = ""
|
| 25 |
+
private var mUserName: String = ""
|
| 26 |
+
|
| 27 |
+
private var mContext = context
|
| 28 |
+
private var mListener: OnSMSClickListener? = null
|
| 29 |
+
private var mContactDetailVisibilityListener: OnContactDetailVisibilityListener? = null
|
| 30 |
+
|
| 31 |
+
init {
|
| 32 |
+
initView()
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
fun setOnSMSClickListener(listener: OnSMSClickListener) {
|
| 36 |
+
mListener = listener
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
fun setOnContactDetailVisibilityListener(listener: OnContactDetailVisibilityListener) {
|
| 40 |
+
mContactDetailVisibilityListener = listener
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
private fun initView() {
|
| 44 |
+
LayoutInflater.from(context).inflate(R.layout.item_contact_detail, this, true)
|
| 45 |
+
|
| 46 |
+
mTvPhoneNumber = findViewById(R.id.tv_phone_number)
|
| 47 |
+
mTvPhoneType = findViewById(R.id.tv_phone_type)
|
| 48 |
+
|
| 49 |
+
findViewById<ImageView>(R.id.btn_voice_call).setOnClickListener(this)
|
| 50 |
+
findViewById<ImageView>(R.id.btn_send_message).setOnClickListener(this)
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
fun setContactDetailItemInfo(phoneNumber: String, username: String) {
|
| 54 |
+
mPhoneNumber = phoneNumber
|
| 55 |
+
mUserName = username
|
| 56 |
+
mTvPhoneNumber.text = phoneNumber
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
override fun onClick(view: View?) {
|
| 60 |
+
when (view!!.id) {
|
| 61 |
+
R.id.btn_voice_call -> {
|
| 62 |
+
mContactDetailVisibilityListener!!.invisible()
|
| 63 |
+
mListener!!.onVoiceCallListener(mPhoneNumber, mUserName)
|
| 64 |
+
doVoiceCall(mPhoneNumber)
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
R.id.btn_send_message -> {
|
| 68 |
+
mListener!!.onSMSClickListener(mPhoneNumber)
|
| 69 |
+
mContactDetailVisibilityListener!!.invisible()
|
| 70 |
+
}
|
| 71 |
+
}
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
private fun doVoiceCall(phoneNumber: String) {
|
| 75 |
+
val callIntent = Intent(Intent.ACTION_CALL, Uri.parse("tel:$phoneNumber"))
|
| 76 |
+
mContext.startActivity(callIntent)
|
| 77 |
+
return
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
fun openMessagingApp(phoneNumber: String, message: String) {
|
| 81 |
+
val uri = Uri.parse("sms:$phoneNumber")
|
| 82 |
+
val intent = Intent(Intent.ACTION_VIEW, uri).apply {
|
| 83 |
+
putExtra("sms_body", message)
|
| 84 |
+
}
|
| 85 |
+
mContext.startActivity(intent)
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
private fun doVideoCall(phoneNumber: String) {
|
| 89 |
+
val callIntent = Intent(Intent.ACTION_DIAL, Uri.parse("tel:$phoneNumber"))
|
| 90 |
+
callIntent.putExtra(
|
| 91 |
+
"android.telecom.extra.START_CALL_WITH_VIDEO_STATE",
|
| 92 |
+
VideoProfile.STATE_BIDIRECTIONAL
|
| 93 |
+
)
|
| 94 |
+
val hasCamera =
|
| 95 |
+
context.packageManager?.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT) ?: false
|
| 96 |
+
val hasTelephony =
|
| 97 |
+
context.packageManager?.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) ?: false
|
| 98 |
+
val canMakeVideoCalls = hasCamera && hasTelephony
|
| 99 |
+
|
| 100 |
+
if (canMakeVideoCalls) {
|
| 101 |
+
context.startActivity(callIntent)
|
| 102 |
+
}
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
interface OnSMSClickListener {
|
| 106 |
+
fun onSMSClickListener(phoneNumber: String)
|
| 107 |
+
fun onVoiceCallListener(phoneNumber: String, toName: String)
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
interface OnContactDetailVisibilityListener {
|
| 111 |
+
fun invisible()
|
| 112 |
+
}
|
| 113 |
+
}
|
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/widgets/ContactDetailWidget.kt
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.matthaigh27.chatgptwrapper.widgets
|
| 2 |
+
|
| 3 |
+
import android.content.ContentUris
|
| 4 |
+
import android.content.Context
|
| 5 |
+
import android.content.Intent
|
| 6 |
+
import android.provider.ContactsContract
|
| 7 |
+
import android.util.AttributeSet
|
| 8 |
+
import android.view.LayoutInflater
|
| 9 |
+
import android.view.View
|
| 10 |
+
import android.widget.ImageView
|
| 11 |
+
import android.widget.LinearLayout
|
| 12 |
+
import android.widget.TextView
|
| 13 |
+
import androidx.constraintlayout.widget.ConstraintLayout
|
| 14 |
+
import androidx.core.view.forEach
|
| 15 |
+
import com.google.android.material.bottomsheet.BottomSheetDialog
|
| 16 |
+
import com.matthaigh27.chatgptwrapper.R
|
| 17 |
+
import com.matthaigh27.chatgptwrapper.models.common.ContactModel
|
| 18 |
+
import com.matthaigh27.chatgptwrapper.utils.Utils
|
| 19 |
+
|
| 20 |
+
class ContactDetailWidget(
|
| 21 |
+
context: Context, contactModel: ContactModel, smsClickListener: ContactDetailItem.OnSMSClickListener
|
| 22 |
+
) : BottomSheetDialog(context), View.OnClickListener {
|
| 23 |
+
|
| 24 |
+
private lateinit var mAvatar: ImageView
|
| 25 |
+
private lateinit var mDisplayName: TextView
|
| 26 |
+
private lateinit var mBtnEditContact: ImageView
|
| 27 |
+
private lateinit var mLlPhones: LinearLayout
|
| 28 |
+
|
| 29 |
+
private var mContactModel = contactModel
|
| 30 |
+
|
| 31 |
+
private var mOnSMSClickListener: ContactDetailItem.OnSMSClickListener = smsClickListener
|
| 32 |
+
|
| 33 |
+
init {
|
| 34 |
+
initView()
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
private fun initView() {
|
| 39 |
+
setContentView(R.layout.view_contact_detail)
|
| 40 |
+
|
| 41 |
+
mDisplayName = findViewById(R.id.tv_displayname)!!
|
| 42 |
+
mBtnEditContact = findViewById(R.id.btn_edit_contact)!!
|
| 43 |
+
mLlPhones = findViewById(R.id.ll_contacts)!!
|
| 44 |
+
|
| 45 |
+
mLlPhones.removeAllViews()
|
| 46 |
+
mDisplayName.text = mContactModel.name
|
| 47 |
+
mContactModel.phoneList!!.forEach { phoneNumber ->
|
| 48 |
+
val contactDetailItem = ContactDetailItem(context)
|
| 49 |
+
contactDetailItem.setContactDetailItemInfo(phoneNumber, mContactModel.name)
|
| 50 |
+
contactDetailItem.setOnSMSClickListener(mOnSMSClickListener)
|
| 51 |
+
contactDetailItem.setOnContactDetailVisibilityListener(object:
|
| 52 |
+
ContactDetailItem.OnContactDetailVisibilityListener {
|
| 53 |
+
override fun invisible() {
|
| 54 |
+
this@ContactDetailWidget.dismiss()
|
| 55 |
+
}
|
| 56 |
+
})
|
| 57 |
+
mLlPhones.addView(contactDetailItem)
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
mBtnEditContact.setOnClickListener(this)
|
| 61 |
+
mAvatar = findViewById(R.id.iv_avatar)!!
|
| 62 |
+
Utils.instance.setContactAvatar(mContactModel.id.toLong(), context, mAvatar)
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
override fun onClick(view: View?) {
|
| 66 |
+
when (view!!.id) {
|
| 67 |
+
R.id.btn_edit_contact -> {
|
| 68 |
+
goToContactEditor(mContactModel.id)
|
| 69 |
+
}
|
| 70 |
+
R.id.btn_send_message -> {
|
| 71 |
+
}
|
| 72 |
+
}
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
private fun goToContactEditor(contactId: String) {
|
| 76 |
+
val editIntent = Intent(Intent.ACTION_EDIT)
|
| 77 |
+
val contactUri =
|
| 78 |
+
ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, contactId.toLong())
|
| 79 |
+
editIntent.setData(contactUri)
|
| 80 |
+
context.startActivity(editIntent)
|
| 81 |
+
}
|
| 82 |
+
}
|
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/widgets/HelpCommandEditText.kt
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.matthaigh27.chatgptwrapper.widgets
|
| 2 |
+
|
| 3 |
+
import android.annotation.SuppressLint
|
| 4 |
+
import android.content.Context
|
| 5 |
+
import android.view.ViewGroup
|
| 6 |
+
import android.widget.EditText
|
| 7 |
+
import android.widget.LinearLayout
|
| 8 |
+
import androidx.annotation.Dimension
|
| 9 |
+
import com.matthaigh27.chatgptwrapper.R
|
| 10 |
+
|
| 11 |
+
@SuppressLint("AppCompatCustomView")
|
| 12 |
+
class HelpCommandEditText(context: Context) : EditText(context) {
|
| 13 |
+
init {
|
| 14 |
+
val layoutParams: LinearLayout.LayoutParams = LinearLayout.LayoutParams(
|
| 15 |
+
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT
|
| 16 |
+
)
|
| 17 |
+
val marginBottom =
|
| 18 |
+
context.resources.getDimensionPixelSize(R.dimen.view_help_prompt_key_edittext_margin_bottom)
|
| 19 |
+
layoutParams.setMargins(0, 0, 0, marginBottom)
|
| 20 |
+
this.layoutParams = layoutParams
|
| 21 |
+
|
| 22 |
+
minHeight =
|
| 23 |
+
context.resources.getDimensionPixelSize(R.dimen.view_help_prompt_key_edittext_minheight)
|
| 24 |
+
|
| 25 |
+
val paddingStart =
|
| 26 |
+
context.resources.getDimensionPixelSize(R.dimen.view_help_prompt_edittext_padding_start)
|
| 27 |
+
setPadding(paddingStart, 0, 0, 0)
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
setTextSize(
|
| 31 |
+
Dimension.DP,
|
| 32 |
+
context.resources.getDimensionPixelSize(R.dimen.view_help_prompt_edittext_fontsize)
|
| 33 |
+
.toFloat()
|
| 34 |
+
)
|
| 35 |
+
setTextColor(context.getColor(R.color.primary))
|
| 36 |
+
setHintTextColor(context.getColor(R.color.view_help_prompt_edittext_hint_color))
|
| 37 |
+
background = context.getDrawable(R.drawable.background_view_help_prompt_edittext)
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
fun initView(keyName: String) {
|
| 41 |
+
hint = keyName
|
| 42 |
+
tag = keyName
|
| 43 |
+
}
|
| 44 |
+
}
|
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/widgets/HelpPromptWidget.kt
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.matthaigh27.chatgptwrapper.widgets
|
| 2 |
+
|
| 3 |
+
import android.app.Dialog
|
| 4 |
+
import android.content.Context
|
| 5 |
+
import android.os.Bundle
|
| 6 |
+
import android.util.Log
|
| 7 |
+
import android.view.View
|
| 8 |
+
import android.view.ViewGroup
|
| 9 |
+
import android.view.Window
|
| 10 |
+
import android.widget.ArrayAdapter
|
| 11 |
+
import android.widget.AutoCompleteTextView
|
| 12 |
+
import android.widget.Button
|
| 13 |
+
import android.widget.EditText
|
| 14 |
+
import android.widget.LinearLayout
|
| 15 |
+
import android.widget.TextView
|
| 16 |
+
import androidx.constraintlayout.widget.ConstraintLayout
|
| 17 |
+
import com.matthaigh27.chatgptwrapper.R
|
| 18 |
+
import com.matthaigh27.chatgptwrapper.models.common.HelpPromptModel
|
| 19 |
+
|
| 20 |
+
class HelpPromptWidget(context: Context, model: HelpPromptModel) : ConstraintLayout(context),
|
| 21 |
+
View.OnClickListener {
|
| 22 |
+
private lateinit var mListener: OnHelpPromptListener
|
| 23 |
+
private lateinit var mLlPromptKeys: LinearLayout
|
| 24 |
+
private lateinit var mTvKeysTitle: TextView
|
| 25 |
+
|
| 26 |
+
private var mListEtPrompt: ArrayList<EditText>? = null
|
| 27 |
+
|
| 28 |
+
val mModel: HelpPromptModel = model
|
| 29 |
+
|
| 30 |
+
init {
|
| 31 |
+
initView()
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
fun setOnClickListener(listener: OnHelpPromptListener) {
|
| 35 |
+
mListener = listener
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
private fun initView() {
|
| 39 |
+
inflate(context, R.layout.view_help_prompt, this)
|
| 40 |
+
|
| 41 |
+
layoutParams = LayoutParams(
|
| 42 |
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
| 43 |
+
ViewGroup.LayoutParams.WRAP_CONTENT
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
mLlPromptKeys = findViewById(R.id.ll_prompt_keys)
|
| 47 |
+
mTvKeysTitle = findViewById(R.id.tv_keys_title)
|
| 48 |
+
mTvKeysTitle.setText(mModel.name)
|
| 49 |
+
|
| 50 |
+
findViewById<Button>(R.id.btn_ok).setOnClickListener(this)
|
| 51 |
+
findViewById<Button>(R.id.btn_cancel).setOnClickListener(this)
|
| 52 |
+
|
| 53 |
+
initPromptList()
|
| 54 |
+
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
private fun initPromptList() {
|
| 58 |
+
if (mModel.tags!!.size > 0) {
|
| 59 |
+
mLlPromptKeys.removeAllViews()
|
| 60 |
+
mListEtPrompt = ArrayList()
|
| 61 |
+
|
| 62 |
+
for (i in 0 until mModel.tags!!.size) {
|
| 63 |
+
val etKey = HelpCommandEditText(context)
|
| 64 |
+
etKey.initView(mModel.tags!![i])
|
| 65 |
+
mLlPromptKeys.addView(etKey)
|
| 66 |
+
mListEtPrompt!!.add(etKey)
|
| 67 |
+
}
|
| 68 |
+
}
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
override fun onClick(view: View?) {
|
| 72 |
+
when (view?.id) {
|
| 73 |
+
R.id.btn_ok -> {
|
| 74 |
+
outputCompletePrompt()
|
| 75 |
+
}
|
| 76 |
+
R.id.btn_cancel -> {
|
| 77 |
+
mListener.onCancel()
|
| 78 |
+
}
|
| 79 |
+
}
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
private fun outputCompletePrompt() {
|
| 83 |
+
var promptTemplate = mModel.prompt
|
| 84 |
+
if(mModel.tags!!.size > 0) {
|
| 85 |
+
mListEtPrompt!!.forEach { etKeyPrompt ->
|
| 86 |
+
val prompt = etKeyPrompt.text.toString()
|
| 87 |
+
if(prompt.isEmpty()) return
|
| 88 |
+
promptTemplate = promptTemplate.replace(etKeyPrompt.tag.toString(), prompt)
|
| 89 |
+
}
|
| 90 |
+
}
|
| 91 |
+
mListener.onSuccess(promptTemplate)
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
interface OnHelpPromptListener {
|
| 95 |
+
fun onSuccess(prompt: String)
|
| 96 |
+
fun onCancel()
|
| 97 |
+
}
|
| 98 |
+
}
|
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/widgets/ImagePickerWidget.kt
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.matthaigh27.chatgptwrapper.widgets
|
| 2 |
+
|
| 3 |
+
import android.content.Context
|
| 4 |
+
import android.view.View
|
| 5 |
+
import android.view.View.OnClickListener
|
| 6 |
+
import android.view.ViewGroup
|
| 7 |
+
import android.widget.LinearLayout
|
| 8 |
+
import androidx.constraintlayout.widget.ConstraintLayout
|
| 9 |
+
import com.matthaigh27.chatgptwrapper.R
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class ImagePickerWidget(context: Context) : LinearLayout(context), OnClickListener {
|
| 13 |
+
|
| 14 |
+
private lateinit var mClickListener: OnPositiveButtonClickListener
|
| 15 |
+
|
| 16 |
+
init {
|
| 17 |
+
initView()
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
fun setOnClickListener(listener: OnPositiveButtonClickListener) {
|
| 21 |
+
mClickListener = listener
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
private fun initView() {
|
| 25 |
+
inflate(context, R.layout.view_image_picker, this)
|
| 26 |
+
|
| 27 |
+
layoutParams = ConstraintLayout.LayoutParams(
|
| 28 |
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
| 29 |
+
ViewGroup.LayoutParams.WRAP_CONTENT
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
findViewById<LinearLayout>(R.id.lytCameraPick).setOnClickListener(this)
|
| 33 |
+
findViewById<LinearLayout>(R.id.lytGalleryPick).setOnClickListener(this)
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
override fun onClick(view: View?) {
|
| 37 |
+
when (view?.id) {
|
| 38 |
+
R.id.lytCameraPick -> {
|
| 39 |
+
mClickListener.onPositiveBtnClick(true)
|
| 40 |
+
}
|
| 41 |
+
R.id.lytGalleryPick -> {
|
| 42 |
+
mClickListener.onPositiveBtnClick(false)
|
| 43 |
+
}
|
| 44 |
+
}
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
/**
|
| 48 |
+
* callback function invoked when filepickerdialog buttons are pressed
|
| 49 |
+
*/
|
| 50 |
+
interface OnPositiveButtonClickListener {
|
| 51 |
+
fun onPositiveBtnClick(isCamera: Boolean?)
|
| 52 |
+
}
|
| 53 |
+
}
|
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/widgets/SearchContactWidget.kt
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.matthaigh27.chatgptwrapper.widgets
|
| 2 |
+
|
| 3 |
+
import android.content.ContentUris
|
| 4 |
+
import android.content.Context
|
| 5 |
+
import android.net.Uri
|
| 6 |
+
import android.provider.ContactsContract
|
| 7 |
+
import android.util.AttributeSet
|
| 8 |
+
import android.view.View
|
| 9 |
+
import android.view.ViewGroup
|
| 10 |
+
import android.widget.ImageView
|
| 11 |
+
import android.widget.LinearLayout
|
| 12 |
+
import android.widget.TextView
|
| 13 |
+
import androidx.constraintlayout.widget.ConstraintLayout
|
| 14 |
+
import com.bumptech.glide.Glide
|
| 15 |
+
import com.google.android.material.bottomsheet.BottomSheetDialog
|
| 16 |
+
import com.matthaigh27.chatgptwrapper.R
|
| 17 |
+
import com.matthaigh27.chatgptwrapper.models.common.ContactModel
|
| 18 |
+
import com.matthaigh27.chatgptwrapper.utils.Utils
|
| 19 |
+
import de.hdodenhof.circleimageview.CircleImageView
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class SearchContactWidget(
|
| 23 |
+
context: Context, cotactModel: ContactModel, attrs: AttributeSet?
|
| 24 |
+
) : ConstraintLayout(context, attrs), View.OnClickListener {
|
| 25 |
+
|
| 26 |
+
private val mContext: Context
|
| 27 |
+
|
| 28 |
+
private val civInfoAvatar: CircleImageView
|
| 29 |
+
private val tvInfoName: TextView
|
| 30 |
+
|
| 31 |
+
private var mContact:ContactModel = cotactModel
|
| 32 |
+
var mSMSOnClickListener: ContactDetailItem.OnSMSClickListener? = null
|
| 33 |
+
|
| 34 |
+
init {
|
| 35 |
+
inflate(context, R.layout.view_search_contact, this)
|
| 36 |
+
mContext = context
|
| 37 |
+
|
| 38 |
+
layoutParams = LayoutParams(
|
| 39 |
+
ViewGroup.LayoutParams.WRAP_CONTENT,
|
| 40 |
+
ViewGroup.LayoutParams.WRAP_CONTENT
|
| 41 |
+
)
|
| 42 |
+
|
| 43 |
+
civInfoAvatar = findViewById(R.id.civ_avatar)
|
| 44 |
+
tvInfoName = findViewById(R.id.tv_info_name)
|
| 45 |
+
|
| 46 |
+
tvInfoName.text = mContact.name
|
| 47 |
+
Utils.instance.setContactAvatar(mContact.id.toLong(), mContext, civInfoAvatar)
|
| 48 |
+
|
| 49 |
+
this.setOnClickListener(this)
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
private fun showContactDetailView() {
|
| 55 |
+
val bottomSheetDialog = ContactDetailWidget(mContext, mContact, mSMSOnClickListener!!)
|
| 56 |
+
bottomSheetDialog.show()
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
override fun onClick(view: View?) {
|
| 60 |
+
showContactDetailView()
|
| 61 |
+
}
|
| 62 |
+
}
|
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/widgets/SmsEditorWidget.kt
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.matthaigh27.chatgptwrapper.widgets
|
| 2 |
+
|
| 3 |
+
import android.content.Context
|
| 4 |
+
import android.util.AttributeSet
|
| 5 |
+
import android.view.View
|
| 6 |
+
import android.widget.Button
|
| 7 |
+
import android.widget.EditText
|
| 8 |
+
import android.widget.Toast
|
| 9 |
+
import androidx.constraintlayout.widget.ConstraintLayout
|
| 10 |
+
import com.matthaigh27.chatgptwrapper.R
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class SmsEditorWidget(
|
| 14 |
+
context: Context, attrs: AttributeSet?
|
| 15 |
+
) : ConstraintLayout(context, attrs) {
|
| 16 |
+
|
| 17 |
+
private val mContext: Context
|
| 18 |
+
|
| 19 |
+
private val etToName: EditText
|
| 20 |
+
private val etMessage: EditText
|
| 21 |
+
|
| 22 |
+
private val btnConfirm: Button
|
| 23 |
+
private val btnCancel: Button
|
| 24 |
+
|
| 25 |
+
private var mListener: OnClickListener? = null
|
| 26 |
+
|
| 27 |
+
init {
|
| 28 |
+
inflate(context, R.layout.view_sms_editor, this)
|
| 29 |
+
mContext = context
|
| 30 |
+
|
| 31 |
+
layoutParams = LayoutParams(
|
| 32 |
+
android.view.ViewGroup.LayoutParams.MATCH_PARENT,
|
| 33 |
+
android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
etToName = findViewById(R.id.et_to_name)
|
| 37 |
+
etMessage = findViewById(R.id.et_message)
|
| 38 |
+
|
| 39 |
+
btnConfirm = findViewById(R.id.btn_confirm)
|
| 40 |
+
btnCancel = findViewById(R.id.btn_cancel)
|
| 41 |
+
|
| 42 |
+
btnConfirm.setOnClickListener {
|
| 43 |
+
if (etToName.text.toString().isEmpty() || etMessage.text.toString().isEmpty()) {
|
| 44 |
+
Toast.makeText(
|
| 45 |
+
mContext, "Please input phone number and message.", Toast.LENGTH_SHORT
|
| 46 |
+
).show()
|
| 47 |
+
return@setOnClickListener
|
| 48 |
+
}
|
| 49 |
+
mListener!!.confirmSMS(etToName.text.toString(), etMessage.text.toString())
|
| 50 |
+
hide()
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
btnCancel.setOnClickListener {
|
| 54 |
+
hide()
|
| 55 |
+
mListener!!.cancelSMS()
|
| 56 |
+
}
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
fun setToUserName(name: String) {
|
| 60 |
+
etToName.setText(name)
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
fun hide() {
|
| 64 |
+
this.visibility = View.GONE
|
| 65 |
+
etToName.setText("")
|
| 66 |
+
etMessage.setText("")
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
fun setOnClickListener(listener: OnClickListener) {
|
| 70 |
+
mListener = listener
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
interface OnClickListener {
|
| 74 |
+
fun confirmSMS(phonenumber: String, message: String);
|
| 75 |
+
fun cancelSMS();
|
| 76 |
+
}
|
| 77 |
+
}
|
Android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
| 2 |
+
xmlns:aapt="http://schemas.android.com/aapt"
|
| 3 |
+
android:width="108dp"
|
| 4 |
+
android:height="108dp"
|
| 5 |
+
android:viewportWidth="108"
|
| 6 |
+
android:viewportHeight="108">
|
| 7 |
+
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
| 8 |
+
<aapt:attr name="android:fillColor">
|
| 9 |
+
<gradient
|
| 10 |
+
android:endX="85.84757"
|
| 11 |
+
android:endY="92.4963"
|
| 12 |
+
android:startX="42.9492"
|
| 13 |
+
android:startY="49.59793"
|
| 14 |
+
android:type="linear">
|
| 15 |
+
<item
|
| 16 |
+
android:color="#44000000"
|
| 17 |
+
android:offset="0.0" />
|
| 18 |
+
<item
|
| 19 |
+
android:color="#00000000"
|
| 20 |
+
android:offset="1.0" />
|
| 21 |
+
</gradient>
|
| 22 |
+
</aapt:attr>
|
| 23 |
+
</path>
|
| 24 |
+
<path
|
| 25 |
+
android:fillColor="#FFFFFF"
|
| 26 |
+
android:fillType="nonZero"
|
| 27 |
+
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
| 28 |
+
android:strokeWidth="1"
|
| 29 |
+
android:strokeColor="#00000000" />
|
| 30 |
+
</vector>
|
Android/app/src/main/res/drawable/background_common_rounded_button.xml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="utf-8"?>
|
| 2 |
+
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
| 3 |
+
android:shape="rectangle">
|
| 4 |
+
<solid android:color="@color/common_rounded_button_bgcolor" />
|
| 5 |
+
<corners android:radius="@dimen/control_button_common_radius" />
|
| 6 |
+
</shape>
|
Android/app/src/main/res/drawable/background_content_top.xml
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="utf-8"?>
|
| 2 |
+
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
| 3 |
+
android:shape="rectangle">
|
| 4 |
+
<solid android:color="@color/chat_content_top_bgcolor" />
|
| 5 |
+
<corners
|
| 6 |
+
android:bottomLeftRadius="@dimen/chat_content_top_radius"
|
| 7 |
+
android:bottomRightRadius="@dimen/chat_content_top_radius" />
|
| 8 |
+
</shape>
|
Android/app/src/main/res/drawable/background_control_button_common.xml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="utf-8"?>
|
| 2 |
+
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
| 3 |
+
android:shape="rectangle">
|
| 4 |
+
<solid android:color="@color/control_button_common_bgcolor" />
|
| 5 |
+
<corners android:radius="@dimen/control_button_common_radius" />
|
| 6 |
+
</shape>
|
Android/app/src/main/res/drawable/background_dialog_common_confirm.xml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="utf-8"?>
|
| 2 |
+
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
| 3 |
+
android:shape="rectangle">
|
| 4 |
+
<solid android:color="@color/dialog_common_confirm_bg_color" />
|
| 5 |
+
<corners android:radius="@dimen/dialog_common_confirm_radius" />
|
| 6 |
+
</shape>
|
Android/app/src/main/res/drawable/background_feedback_layout.xml
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="utf-8"?>
|
| 2 |
+
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
| 3 |
+
android:shape="rectangle">
|
| 4 |
+
<stroke
|
| 5 |
+
android:width="@dimen/chat_feedback_stroke_width"
|
| 6 |
+
android:color="@color/feedback_common_color" />
|
| 7 |
+
<corners android:radius="@dimen/chat_feedback_radius" />
|
| 8 |
+
</shape>
|
Android/app/src/main/res/drawable/background_load_assets_button_cancel.xml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="utf-8"?>
|
| 2 |
+
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
| 3 |
+
android:shape="rectangle">
|
| 4 |
+
<solid android:color="@color/load_assets_button_bgcolor" />
|
| 5 |
+
<corners android:radius="@dimen/load_assets_button_radius" />
|
| 6 |
+
</shape>
|