├── cat-drive-android
├── app
│ ├── .gitignore
│ ├── src
│ │ ├── main
│ │ │ ├── res
│ │ │ │ ├── values
│ │ │ │ │ ├── arrays.xml
│ │ │ │ │ ├── dimens.xml
│ │ │ │ │ ├── colors.xml
│ │ │ │ │ ├── themes.xml
│ │ │ │ │ └── strings.xml
│ │ │ │ ├── drawable
│ │ │ │ │ ├── bg.jpg
│ │ │ │ │ ├── catface.png
│ │ │ │ │ ├── roundabout.bmp
│ │ │ │ │ ├── ic_home_black_24dp.xml
│ │ │ │ │ ├── ic_dashboard_black_24dp.xml
│ │ │ │ │ ├── ic_notifications_black_24dp.xml
│ │ │ │ │ └── ic_launcher_background.xml
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ ├── ic_launcher.webp
│ │ │ │ │ └── ic_launcher_round.webp
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ ├── ic_launcher.webp
│ │ │ │ │ └── ic_launcher_round.webp
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ ├── ic_launcher.webp
│ │ │ │ │ └── ic_launcher_round.webp
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ ├── ic_launcher.webp
│ │ │ │ │ └── ic_launcher_round.webp
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ ├── ic_launcher.webp
│ │ │ │ │ └── ic_launcher_round.webp
│ │ │ │ ├── mipmap-anydpi-v26
│ │ │ │ │ ├── ic_launcher.xml
│ │ │ │ │ └── ic_launcher_round.xml
│ │ │ │ ├── menu
│ │ │ │ │ └── bottom_nav_menu.xml
│ │ │ │ ├── xml
│ │ │ │ │ ├── backup_rules.xml
│ │ │ │ │ ├── data_extraction_rules.xml
│ │ │ │ │ └── root_preferences.xml
│ │ │ │ ├── navigation
│ │ │ │ │ └── mobile_navigation.xml
│ │ │ │ ├── values-night
│ │ │ │ │ └── themes.xml
│ │ │ │ ├── layout
│ │ │ │ │ ├── activity_device_selection.xml
│ │ │ │ │ ├── activity_main.xml
│ │ │ │ │ ├── device_row_item.xml
│ │ │ │ │ └── fragment_home.xml
│ │ │ │ └── drawable-v24
│ │ │ │ │ └── ic_launcher_foreground.xml
│ │ │ ├── java
│ │ │ │ └── com
│ │ │ │ │ └── meomeo
│ │ │ │ │ └── catdrive
│ │ │ │ │ ├── ui
│ │ │ │ │ ├── home
│ │ │ │ │ │ ├── HomeViewModel.kt
│ │ │ │ │ │ └── HomeFragment.kt
│ │ │ │ │ ├── ActivityViewModel.kt
│ │ │ │ │ ├── DeviceSelectionActivity.kt
│ │ │ │ │ └── settings
│ │ │ │ │ │ └── SettingsFragment.kt
│ │ │ │ │ ├── lib
│ │ │ │ │ ├── Intents.kt
│ │ │ │ │ ├── NavigationNotification.kt
│ │ │ │ │ ├── NavigationData.kt
│ │ │ │ │ ├── ParserHelper.kt
│ │ │ │ │ ├── BluetoothSerial.kt
│ │ │ │ │ ├── IntrospectionUtils.kt
│ │ │ │ │ ├── BitmapHelper.kt
│ │ │ │ │ ├── GMapsNotification.kt
│ │ │ │ │ └── CustomSerializers.kt
│ │ │ │ │ ├── MeowGoogleMapNotificationListener.kt
│ │ │ │ │ ├── utils
│ │ │ │ │ ├── ServiceManager.kt
│ │ │ │ │ └── PermissionCheck.kt
│ │ │ │ │ ├── service
│ │ │ │ │ ├── NavigationListener.kt
│ │ │ │ │ └── BroadcastService.kt
│ │ │ │ │ └── MainActivity.kt
│ │ │ └── AndroidManifest.xml
│ │ └── debug
│ │ │ ├── ic_launcher-playstore.png
│ │ │ └── res
│ │ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_round.png
│ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_round.png
│ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_round.png
│ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_round.png
│ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_round.png
│ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-anydpi-v26
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ │ └── drawable
│ │ │ └── ic_launcher_background.xml
│ ├── release
│ │ ├── baselineProfiles
│ │ │ ├── 0
│ │ │ │ └── app-release.dm
│ │ │ └── 1
│ │ │ │ └── app-release.dm
│ │ └── output-metadata.json
│ ├── proguard-rules.pro
│ └── build.gradle
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── build.gradle
├── settings.gradle
├── .gitignore
├── gradle.properties
├── gradlew.bat
└── gradlew
├── images
└── arduino-v2-config.png
├── binaries
└── cat-drive-esp32.ino.merged.bin
├── cat-drive-esp32
├── convert_font.sh
├── ota.h
├── Scheduler.h
├── splash.h
├── pages
│ └── index.html.h
├── ota.cpp
├── scrollabletext.h
└── .gitignore
├── LICENSE
└── README.md
/cat-drive-android/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/res/values/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/images/arduino-v2-config.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maisonsmd/esp32-google-maps-oled-non-ble/HEAD/images/arduino-v2-config.png
--------------------------------------------------------------------------------
/binaries/cat-drive-esp32.ino.merged.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maisonsmd/esp32-google-maps-oled-non-ble/HEAD/binaries/cat-drive-esp32.ino.merged.bin
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/res/drawable/bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maisonsmd/esp32-google-maps-oled-non-ble/HEAD/cat-drive-android/app/src/main/res/drawable/bg.jpg
--------------------------------------------------------------------------------
/cat-drive-android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maisonsmd/esp32-google-maps-oled-non-ble/HEAD/cat-drive-android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/res/drawable/catface.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maisonsmd/esp32-google-maps-oled-non-ble/HEAD/cat-drive-android/app/src/main/res/drawable/catface.png
--------------------------------------------------------------------------------
/cat-drive-android/app/src/debug/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maisonsmd/esp32-google-maps-oled-non-ble/HEAD/cat-drive-android/app/src/debug/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/res/drawable/roundabout.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maisonsmd/esp32-google-maps-oled-non-ble/HEAD/cat-drive-android/app/src/main/res/drawable/roundabout.bmp
--------------------------------------------------------------------------------
/cat-drive-android/app/release/baselineProfiles/0/app-release.dm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maisonsmd/esp32-google-maps-oled-non-ble/HEAD/cat-drive-android/app/release/baselineProfiles/0/app-release.dm
--------------------------------------------------------------------------------
/cat-drive-android/app/release/baselineProfiles/1/app-release.dm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maisonsmd/esp32-google-maps-oled-non-ble/HEAD/cat-drive-android/app/release/baselineProfiles/1/app-release.dm
--------------------------------------------------------------------------------
/cat-drive-android/app/src/debug/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maisonsmd/esp32-google-maps-oled-non-ble/HEAD/cat-drive-android/app/src/debug/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/cat-drive-android/app/src/debug/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maisonsmd/esp32-google-maps-oled-non-ble/HEAD/cat-drive-android/app/src/debug/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maisonsmd/esp32-google-maps-oled-non-ble/HEAD/cat-drive-android/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maisonsmd/esp32-google-maps-oled-non-ble/HEAD/cat-drive-android/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/cat-drive-android/app/src/debug/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maisonsmd/esp32-google-maps-oled-non-ble/HEAD/cat-drive-android/app/src/debug/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/cat-drive-android/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maisonsmd/esp32-google-maps-oled-non-ble/HEAD/cat-drive-android/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/cat-drive-android/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maisonsmd/esp32-google-maps-oled-non-ble/HEAD/cat-drive-android/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maisonsmd/esp32-google-maps-oled-non-ble/HEAD/cat-drive-android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maisonsmd/esp32-google-maps-oled-non-ble/HEAD/cat-drive-android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maisonsmd/esp32-google-maps-oled-non-ble/HEAD/cat-drive-android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/cat-drive-android/app/src/debug/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maisonsmd/esp32-google-maps-oled-non-ble/HEAD/cat-drive-android/app/src/debug/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/cat-drive-android/app/src/debug/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maisonsmd/esp32-google-maps-oled-non-ble/HEAD/cat-drive-android/app/src/debug/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/cat-drive-android/app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maisonsmd/esp32-google-maps-oled-non-ble/HEAD/cat-drive-android/app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/cat-drive-android/app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maisonsmd/esp32-google-maps-oled-non-ble/HEAD/cat-drive-android/app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maisonsmd/esp32-google-maps-oled-non-ble/HEAD/cat-drive-android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maisonsmd/esp32-google-maps-oled-non-ble/HEAD/cat-drive-android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maisonsmd/esp32-google-maps-oled-non-ble/HEAD/cat-drive-android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maisonsmd/esp32-google-maps-oled-non-ble/HEAD/cat-drive-android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/cat-drive-android/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maisonsmd/esp32-google-maps-oled-non-ble/HEAD/cat-drive-android/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maisonsmd/esp32-google-maps-oled-non-ble/HEAD/cat-drive-android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/cat-drive-android/app/src/debug/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maisonsmd/esp32-google-maps-oled-non-ble/HEAD/cat-drive-android/app/src/debug/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/cat-drive-android/app/src/debug/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maisonsmd/esp32-google-maps-oled-non-ble/HEAD/cat-drive-android/app/src/debug/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/cat-drive-android/app/src/debug/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maisonsmd/esp32-google-maps-oled-non-ble/HEAD/cat-drive-android/app/src/debug/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/cat-drive-android/app/src/debug/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maisonsmd/esp32-google-maps-oled-non-ble/HEAD/cat-drive-android/app/src/debug/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/cat-drive-android/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maisonsmd/esp32-google-maps-oled-non-ble/HEAD/cat-drive-android/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/cat-drive-esp32/convert_font.sh:
--------------------------------------------------------------------------------
1 | ./otf2bdf/otf2bdf -r 48 -p 16 NotoSans-Regular.otf -o notosans.bdf && ./bdfconv/bdfconv.exe -v -f 1 -m '$00-$7f, $80-$ff, $100-$24f, $1e00-$1eff, $300-$36f, $20ab' -n notosans -o notosans.c notosans.bdf
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
--------------------------------------------------------------------------------
/cat-drive-android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/cat-drive-android/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | plugins {
3 | id 'com.android.application' version '8.8.0' apply false
4 | id 'com.android.library' version '8.8.0' apply false
5 | id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
6 | }
--------------------------------------------------------------------------------
/cat-drive-android/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/cat-drive-android/app/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/res/drawable/ic_home_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/cat-drive-android/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | google()
5 | mavenCentral()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 | rootProject.name = "CatDrive"
16 | include ':app'
17 |
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/java/com/meomeo/catdrive/ui/home/HomeViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.meomeo.catdrive.ui.home
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.lifecycle.ViewModel
6 |
7 | class HomeViewModel : ViewModel() {
8 |
9 | private val _text = MutableLiveData().apply {
10 | value = "This is home Fragment"
11 | }
12 | val text: LiveData = _text
13 | }
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/res/drawable/ic_dashboard_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/res/menu/bottom_nav_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/res/drawable/ic_notifications_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/cat-drive-android/.gitignore:
--------------------------------------------------------------------------------
1 | # Gradle files
2 | .gradle/
3 | build/
4 |
5 | # Local configuration file (sdk path, etc)
6 | local.properties
7 |
8 | # Log/OS Files
9 | *.log
10 |
11 | # Android Studio generated files and folders
12 | captures/
13 | .externalNativeBuild/
14 | .cxx/
15 | *.apk
16 | output.json
17 |
18 | # IntelliJ
19 | *.iml
20 | .idea/
21 | misc.xml
22 | deploymentTargetDropDown.xml
23 | render.experimental.xml
24 |
25 | # Keystore files
26 | *.jks
27 | *.keystore
28 |
29 | # Google Services (e.g. APIs or Firebase)
30 | google-services.json
31 |
32 | # Android Profiling
33 | *.hprof
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/cat-drive-esp32/ota.h:
--------------------------------------------------------------------------------
1 | #ifndef CATDRIVEOTA_H
2 | #define CATDRIVEOTA_H
3 |
4 | #ifdef USE_OTA
5 |
6 | #include
7 |
8 | enum class UpdateStatus {
9 | None,
10 | UpdateStart,
11 | Updating,
12 | UpdateSuccedded,
13 | UpdateFailed
14 | };
15 |
16 | typedef void (*OtaUpdateCallback)(UpdateStatus, String, int, int);
17 |
18 | namespace CatDriveOTA {
19 | void begin(const char* host, const char* ssid, const char* password);
20 | void handle();
21 | String getAPIP();
22 |
23 | UpdateStatus status();
24 | size_t uploadedSize();
25 | size_t totalSize();
26 | String fileName();
27 |
28 | void attachUpdateCallback(OtaUpdateCallback callback);
29 | }
30 |
31 | #endif
32 | #endif
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/java/com/meomeo/catdrive/ui/ActivityViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.meomeo.catdrive.ui
2 |
3 | import android.bluetooth.BluetoothDevice
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.lifecycle.ViewModel
6 | import com.meomeo.catdrive.lib.NavigationData
7 |
8 | class ActivityViewModel : ViewModel() {
9 | val permissionUpdatedTimestamp = MutableLiveData().apply { value = 0 }
10 | val navigationData = MutableLiveData().apply { value = NavigationData() }
11 | val speed = MutableLiveData().apply { value = 0 }
12 | val connectedDevice = MutableLiveData().apply { value = null }
13 | val serviceRunInBackground = MutableLiveData().apply { value = false }
14 | }
--------------------------------------------------------------------------------
/cat-drive-esp32/Scheduler.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #define SCHEDULER_GUARD(__current__, __var__) \
4 | if (__var__ > __current__) __var__ = __current__
5 | #define SCHEDULER_CREATE(__name__) static uint32_t __name__ = 0
6 |
7 | #define TOKENPASTE(x, y) x##y
8 | #define TOKENPASTE2(x, y) TOKENPASTE(x, y)
9 | #define DO_EVERY(duration) \
10 | SCHEDULER_CREATE(TOKENPASTE2(scheduler_, __LINE__)); \
11 | if (onSchedule(SCHEDULER_SOURCE, TOKENPASTE2(scheduler_, __LINE__), duration))
12 |
13 | // this function already guard timekeeper variable
14 | static bool onSchedule(const uint32_t& current, uint32_t& var, const uint32_t& interval) {
15 | SCHEDULER_GUARD(current, var);
16 | if (current < var + interval) return false;
17 |
18 | var = current;
19 | return true;
20 | }
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/res/navigation/mobile_navigation.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
17 |
--------------------------------------------------------------------------------
/cat-drive-android/app/proguard-rules.pro:
--------------------------------------------------------------------------------
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
--------------------------------------------------------------------------------
/cat-drive-android/app/release/output-metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 3,
3 | "artifactType": {
4 | "type": "APK",
5 | "kind": "Directory"
6 | },
7 | "applicationId": "com.meomeo.catdrive",
8 | "variantName": "release",
9 | "elements": [
10 | {
11 | "type": "SINGLE",
12 | "filters": [],
13 | "attributes": [],
14 | "versionCode": 1,
15 | "versionName": "1.0",
16 | "outputFile": "app-release.apk"
17 | }
18 | ],
19 | "elementType": "File",
20 | "baselineProfiles": [
21 | {
22 | "minApi": 28,
23 | "maxApi": 30,
24 | "baselineProfiles": [
25 | "baselineProfiles/1/app-release.dm"
26 | ]
27 | },
28 | {
29 | "minApi": 31,
30 | "maxApi": 2147483647,
31 | "baselineProfiles": [
32 | "baselineProfiles/0/app-release.dm"
33 | ]
34 | }
35 | ],
36 | "minSdkVersionForDexing": 31
37 | }
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/java/com/meomeo/catdrive/lib/Intents.kt:
--------------------------------------------------------------------------------
1 | package com.meomeo.catdrive.lib
2 |
3 | import com.meomeo.catdrive.BuildConfig
4 |
5 | class Intents {
6 | companion object {
7 | private const val appId = BuildConfig.APPLICATION_ID
8 |
9 | const val EnableServices = "${appId}.intent.ENABLE_SERVICES"
10 | const val DisableServices = "${appId}.intent.DISABLE_SERVICES"
11 | const val BindLocalService = "${appId}.intent.LOCAL_BIND"
12 | const val BackgroundServiceStatus = "${appId}.intent.SERVICE_RUNNING"
13 |
14 | const val ConnectDevice = "${appId}.intent.CONNECT_DEVICE"
15 | const val ConnectionUpdate = "${appId}.intent.CONNECTION_UPDATE"
16 |
17 | const val NavigationUpdate = "${appId}.intent.NAVIGATION_UPDATE"
18 | const val GpsUpdate = "${appId}.intent.GPS_UPDATE"
19 |
20 | const val OpenNotificationListenerSettings = "android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/java/com/meomeo/catdrive/lib/NavigationNotification.kt:
--------------------------------------------------------------------------------
1 | package com.meomeo.catdrive.lib
2 |
3 | import android.app.Notification
4 | import android.content.Context
5 | import android.service.notification.StatusBarNotification
6 |
7 | open class NavigationNotification(context: Context, statusBarNotification: StatusBarNotification) {
8 | protected val mNotification: Notification = statusBarNotification.notification
9 | protected val mContext = context
10 | protected var mAppSourceContext: Context = mContext.createPackageContext(
11 | statusBarNotification.packageName,
12 | Context.CONTEXT_IGNORE_SECURITY
13 | )
14 |
15 | private var mNavigationData: NavigationData = NavigationData()
16 | var navigationData
17 | get() = mNavigationData
18 | set(value) {
19 | if (value == mNavigationData)
20 | return
21 | mNavigationData = value
22 | }
23 |
24 | init {
25 | mNavigationData.postTime = NavigationTimestamp(statusBarNotification.postTime)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Son H. Mai (Mason)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/cat-drive-android/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
24 | android.defaults.buildfeatures.buildconfig=true
25 | android.nonFinalResIds=false
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | CatDrive
3 | Home
4 | Settings
5 | Devices
6 |
7 |
8 | Device
9 | Service
10 |
11 | Select a device
12 | No device connected
13 |
14 |
15 | Run services
16 | Requires all permissions allowed & bluetooth turned on
17 | Missing permissions
18 | Go to notification settings
19 |
20 | Accessing notifications
21 | Posting notifications
22 | Accessing location
23 | Accessing bluetooth
24 | Turn light theme on or off
25 | Display light mode
26 | Change device LCD contrast to improve visibility
27 | Display contrast
28 | Speed warning limit
29 |
30 |
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/res/layout/activity_device_selection.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
20 |
21 |
34 |
35 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ESP32 Google Maps, Non-BLE, OLED screen with AdafruitGFX library
2 |
3 | 
4 |
5 | This is the old version of [esp32-google-maps](https://github.com/maisonsmd/esp32-google-maps).
6 |
7 | Since many of you asked for more common OLED display, I just dig out this old code and publish it here.
8 |
9 | ## Difference between this and the new version
10 |
11 | - This version uses AdafruitGFX library, instead of `lvgl` library. You can change the OLED display to any other AdafruitGFX compatible display.
12 | - This version does not use BLE (since I don't have one with BLE support at the time of writing this code). It uses Bluetooth Classic instead.
13 | - The font supports English characters with some of Vietnamese characters, since making fonts for AdafruitGFX is a bit more complicated than `lvgl` library. There are some workarounds in the Android app code for the font issues.
14 |
15 | The OLED display is kind of small, the text is even smaller, mine is 0.96 inch, if you can find a bigger one, it will be better.
16 |
17 | I will merge this code into the new version later when I have time, there will be one Android app for multiple display types.
18 |
19 | ## Display wiring
20 |
21 | | OLED Pin | ESP32 Pin |
22 | |----------|-----------|
23 | | VCC | 3.3V |
24 | | GND | GND |
25 | | SCL | GPIO 22 |
26 | | SDA | GPIO 21 |
27 |
28 | ## Arduino configuration
29 |
30 | 
31 |
32 | ## Prebuilt binary
33 |
34 | Check the `binaries` folder for the prebuilt binary.
35 |
36 | For the Android app, you have to build it yourself and install to your phone via Android Studio, Play Protect won't allow you to install the prebuilt APK anymore.
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
20 |
21 |
35 |
36 |
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/java/com/meomeo/catdrive/lib/NavigationData.kt:
--------------------------------------------------------------------------------
1 | package com.meomeo.catdrive.lib
2 |
3 | import android.graphics.Bitmap
4 | import android.os.Parcelable
5 | import kotlinx.parcelize.Parcelize
6 | import kotlinx.serialization.Serializable
7 |
8 | @Parcelize
9 | @Serializable
10 | data class NavigationDirection(
11 | val nextRoad: String? = null,
12 | val nextRoadAdditionalInfo: String? = null,
13 | val distance: String? = null,
14 | ) : Parcelable
15 |
16 | @Parcelize
17 | @Serializable
18 | data class NavigationEta(
19 | val eta: String? = null,
20 | val ete: String? = null,
21 | val distance: String? = null
22 | ) : Parcelable
23 |
24 | @Parcelize
25 | @Serializable
26 | data class NavigationIcon(
27 | @Serializable(with = BitmapSerializer::class)
28 | val bitmap: Bitmap? = null,
29 | ) : Parcelable, AutoCloseable {
30 | override fun equals(other: Any?): Boolean {
31 | return if (other !is NavigationIcon || bitmap !is Bitmap)
32 | super.equals(other)
33 | else bitmap.sameAs(other.bitmap)
34 | }
35 |
36 | override fun close() {
37 | bitmap?.recycle()
38 | }
39 | }
40 |
41 | @Parcelize
42 | @Serializable
43 | data class NavigationTimestamp(
44 | @Mutable
45 | var timestamp: Long = 0,
46 | ) : Parcelable, MutableContent() {
47 | override fun equals(other: Any?): Boolean {
48 | return super.equals(other)
49 | }
50 | }
51 |
52 | @Parcelize
53 | @Serializable
54 | data class NavigationData(
55 | var nextDirection: NavigationDirection = NavigationDirection(),
56 | var eta: NavigationEta = NavigationEta(),
57 | var actionIcon: NavigationIcon = NavigationIcon(),
58 | @Mutable
59 | var postTime: NavigationTimestamp = NavigationTimestamp(),
60 | ) : Parcelable, Introspectable, MutableContent() {
61 | override fun equals(other: Any?): Boolean {
62 | return super.equals(other)
63 | }
64 | }
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/res/xml/root_preferences.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
9 |
14 |
21 |
22 |
23 |
24 |
29 |
30 |
31 |
32 |
36 |
40 |
44 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/java/com/meomeo/catdrive/MeowGoogleMapNotificationListener.kt:
--------------------------------------------------------------------------------
1 | package com.meomeo.catdrive
2 |
3 | import android.content.Intent
4 | import android.os.Binder
5 | import android.os.IBinder
6 | import androidx.localbroadcastmanager.content.LocalBroadcastManager
7 | import com.meomeo.catdrive.lib.Intents
8 | import com.meomeo.catdrive.lib.NavigationNotification
9 | import com.meomeo.catdrive.service.NavigationListener
10 |
11 | class MeowGoogleMapNotificationListener : NavigationListener() {
12 | private val mBinder = LocalBinder()
13 |
14 | /**
15 | * Class used for the client Binder. Because we know this service always
16 | * runs in the same process as its clients, we don't need to deal with IPC.
17 | */
18 | inner class LocalBinder : Binder() {
19 | // Return this instance of LocalService so clients can call public methods
20 | fun getService(): MeowGoogleMapNotificationListener = this@MeowGoogleMapNotificationListener
21 | }
22 |
23 | override fun onBind(intent: Intent?): IBinder? {
24 | // Bind by activity
25 | if (intent?.action == Intents.BindLocalService) {
26 | return mBinder
27 | }
28 | // Bind by OS
29 | return super.onBind(intent)
30 | }
31 |
32 | override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
33 | if (intent?.action == Intents.EnableServices) {
34 | enabled = true
35 | }
36 | if (intent?.action == Intents.DisableServices) {
37 | enabled = false
38 | }
39 |
40 | return super.onStartCommand(intent, flags, startId)
41 | }
42 |
43 | override fun onNavigationNotificationUpdated(navNotification: NavigationNotification) {
44 | LocalBroadcastManager.getInstance(applicationContext).sendBroadcast(
45 | Intent(Intents.NavigationUpdate).apply {
46 | putExtra("navigation_data", navNotification.navigationData)
47 | }
48 | )
49 | }
50 |
51 | override fun onNavigationNotificationRemoved(navNotification: NavigationNotification) {
52 | LocalBroadcastManager.getInstance(applicationContext).sendBroadcast(
53 | // Empty data (no extras)
54 | Intent(Intents.NavigationUpdate)
55 | )
56 | }
57 | }
--------------------------------------------------------------------------------
/cat-drive-android/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'org.jetbrains.kotlin.android'
4 | id 'org.jetbrains.kotlin.plugin.serialization' version '1.7.20'
5 | id 'kotlin-parcelize'
6 | }
7 |
8 | ext {
9 | kotlin_version = "1.7.20"
10 | kotlix_serialization_version = "1.4.1"
11 | kotlix_coroutines_version = "1.4.2"
12 | lifecycle_version = "2.5.1"
13 | catdrive_version = '1.0.0'
14 | }
15 |
16 | android {
17 | namespace 'com.meomeo.catdrive'
18 | compileSdk 32
19 |
20 | defaultConfig {
21 | applicationId "com.meomeo.catdrive"
22 | minSdk 31
23 | targetSdk 32
24 | versionCode 1
25 | versionName "1.0"
26 | }
27 |
28 | buildTypes {
29 | release {
30 | minifyEnabled false
31 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
32 | }
33 | }
34 | compileOptions {
35 | sourceCompatibility JavaVersion.VERSION_1_8
36 | targetCompatibility JavaVersion.VERSION_1_8
37 | }
38 | kotlinOptions {
39 | jvmTarget = '1.8'
40 | }
41 | buildFeatures {
42 | viewBinding true
43 | }
44 | }
45 |
46 | dependencies {
47 | implementation 'androidx.core:core-ktx:1.7.0'
48 | implementation 'androidx.appcompat:appcompat:1.5.1'
49 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
50 | implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.1'
51 | implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
52 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
53 | implementation 'androidx.navigation:navigation-fragment-ktx:2.5.3'
54 | implementation 'androidx.navigation:navigation-ui-ktx:2.5.3'
55 | implementation 'androidx.preference:preference:1.2.0'
56 |
57 | implementation 'com.jakewharton.timber:timber:4.7.1'
58 |
59 | implementation 'com.google.android.gms:play-services-awareness:19.0.1'
60 | implementation 'com.google.android.material:material:1.7.0'
61 |
62 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlix_coroutines_version"
63 | implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlix_serialization_version"
64 | implementation "org.jetbrains.kotlinx:kotlinx-serialization-cbor:$kotlix_serialization_version"
65 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
66 | implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
67 | }
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/res/layout/device_row_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
17 |
18 |
30 |
31 |
44 |
45 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
22 |
28 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
44 |
45 |
46 |
47 |
48 |
49 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/java/com/meomeo/catdrive/lib/ParserHelper.kt:
--------------------------------------------------------------------------------
1 | package com.meomeo.catdrive.lib
2 |
3 | import android.graphics.Typeface
4 | import android.text.Spanned
5 | import android.text.style.StyleSpan
6 | import androidx.core.text.getSpans
7 | import timber.log.Timber
8 |
9 | data class Span(
10 | val begin: Int,
11 | val end: Int,
12 | val text: String,
13 | val style: Int = Typeface.NORMAL, // style from Typeface
14 | )
15 |
16 | object ParserHelper {
17 | /**
18 | * NOTE: Currently only support one span type: StyleSpan
19 | */
20 | private fun findSpans(input: Spanned): ArrayList {
21 | val results = ArrayList()
22 | var spanBegin = 0
23 | var spanEnd = 0
24 | val len = input.length
25 | while (spanEnd < len) {
26 | spanEnd = input.nextSpanTransition(spanBegin, len, StyleSpan::class.java)
27 | val s = input.substring(spanBegin, spanEnd).trim()
28 | val spans = input.getSpans(spanBegin, spanEnd)
29 | if (spans.isNotEmpty())
30 | results.add(Span(spanBegin, spanEnd, s, spans.first().style))
31 | else
32 | results.add(Span(spanBegin, spanEnd, s))
33 | spanBegin = spanEnd
34 | }
35 | return results
36 | }
37 |
38 | data class SpanSplitResult(
39 | val text: String,
40 | val isKeySpan: Boolean
41 | )
42 |
43 | public fun splitByStyleSpan(input: Spanned, keyStyle: Int, minSpanLength: Int = 0): ArrayList {
44 | val result = ArrayList()
45 | val spans = findSpans(input)
46 |
47 | var begin: Int = 0
48 | var end: Int
49 | var previousSegmentMatched = false
50 | for (span in spans) {
51 | var segmentMatched = false
52 | val segment = input.substring(span.begin, span.end)
53 | if (span.style == keyStyle && (segment.trim().length >= minSpanLength)) {
54 | segmentMatched = true
55 | }
56 |
57 | if (segmentMatched != previousSegmentMatched) {
58 | end = span.begin
59 | val prevSegment = input.substring(begin, end).trim()
60 | if (prevSegment.isNotEmpty())
61 | result.add(SpanSplitResult(prevSegment, previousSegmentMatched))
62 | begin = end
63 | }
64 |
65 | if (span == spans.last()) {
66 | end = span.end
67 | val prevSegment = input.substring(begin, end).trim()
68 | if (prevSegment.isNotEmpty())
69 | result.add(SpanSplitResult(prevSegment, segmentMatched))
70 | }
71 | previousSegmentMatched = segmentMatched
72 | }
73 |
74 | // Timber.w(result.toString())
75 | return result
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/java/com/meomeo/catdrive/utils/ServiceManager.kt:
--------------------------------------------------------------------------------
1 | package com.meomeo.catdrive.utils
2 |
3 | import android.app.ActivityManager
4 | import android.bluetooth.BluetoothDevice
5 | import android.content.Context
6 | import android.content.Intent
7 | import androidx.appcompat.app.AppCompatActivity
8 | import androidx.lifecycle.ViewModelProvider
9 | import com.meomeo.catdrive.MeowGoogleMapNotificationListener
10 | import com.meomeo.catdrive.lib.Intents
11 | import com.meomeo.catdrive.service.BroadcastService
12 | import com.meomeo.catdrive.ui.ActivityViewModel
13 | import timber.log.Timber
14 |
15 | class ServiceManager {
16 | companion object {
17 | fun startBroadcastService(activity: AppCompatActivity) {
18 | Timber.i("start services")
19 | PermissionCheck.requestEnableBluetooth(activity)
20 |
21 | val action = Intents.EnableServices
22 | activity.startService(Intent(activity, BroadcastService::class.java).apply { setAction(action) })
23 | activity.startService(
24 | Intent(
25 | activity, MeowGoogleMapNotificationListener::class.java
26 | ).apply { setAction(action) })
27 | }
28 |
29 | fun requestConnectDevice(activity: AppCompatActivity, device: BluetoothDevice) {
30 | val action = Intents.ConnectDevice
31 | val intent = Intent(activity, BroadcastService::class.java).apply {
32 | setAction(action)
33 | putExtra("device", device)
34 | }
35 | activity.startService(intent)
36 | }
37 |
38 | fun stopBroadcastService(activity: AppCompatActivity) {
39 | Timber.i("stop services")
40 | val action = Intents.DisableServices
41 | // Expect the target service to stop itself
42 | activity.startService(
43 | Intent(
44 | activity, BroadcastService::class.java
45 | ).apply { setAction(action) })
46 | activity.startService(
47 | Intent(
48 | activity, MeowGoogleMapNotificationListener::class.java
49 | ).apply { setAction(action) })
50 | }
51 |
52 | @Suppress("DEPRECATION")
53 | private fun isServiceRunningInBackground(activity: AppCompatActivity, service: Class): Boolean {
54 | val running = (activity.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager).getRunningServices(
55 | Integer.MAX_VALUE
56 | ).any { it.service.className == service.name }
57 | val viewModel = ViewModelProvider(activity)[ActivityViewModel::class.java]
58 | return running && viewModel.serviceRunInBackground.value == true
59 | }
60 |
61 | fun isBroadcastServiceRunningInBackground(activity: AppCompatActivity): Boolean {
62 | return isServiceRunningInBackground(activity, BroadcastService::class.java)
63 | }
64 |
65 | }
66 | }
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/java/com/meomeo/catdrive/ui/home/HomeFragment.kt:
--------------------------------------------------------------------------------
1 | package com.meomeo.catdrive.ui.home
2 |
3 | import android.os.Bundle
4 | import android.util.Size
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import androidx.core.graphics.drawable.toBitmap
9 | import androidx.core.graphics.drawable.toDrawable
10 | import androidx.core.graphics.scale
11 | import androidx.fragment.app.Fragment
12 | import androidx.lifecycle.ViewModelProvider
13 | import com.meomeo.catdrive.R
14 | import com.meomeo.catdrive.databinding.FragmentHomeBinding
15 | import com.meomeo.catdrive.lib.BitmapHelper
16 | import com.meomeo.catdrive.lib.NavigationData
17 | import com.meomeo.catdrive.ui.ActivityViewModel
18 | import timber.log.Timber
19 |
20 |
21 | class HomeFragment : Fragment() {
22 | private var mUiBinding: FragmentHomeBinding? = null
23 | private var mDebugImage = false
24 |
25 | private val binding get() = mUiBinding!!
26 |
27 | private fun displayNavigationData(data: NavigationData?) {
28 | val bh = BitmapHelper()
29 |
30 | val bitmap =
31 | if (!mDebugImage) data?.actionIcon?.bitmap
32 | else resources.getDrawable(R.drawable.roundabout).toBitmap()
33 |
34 | binding.imgTurnIcon.setImageBitmap(bitmap)
35 | /*
36 | val compressed = bh.compressBitmap(bitmap, Size(32, 32))
37 | binding.imgScaled.setImageDrawable(
38 | BitmapHelper.AliasingDrawableWrapper(
39 | bitmap?.scale(32, 32, false)?.toDrawable(resources)
40 | )
41 | )
42 | binding.imgFinal.setImageDrawable(BitmapHelper.AliasingDrawableWrapper(compressed.toDrawable(resources)))
43 | // Timber.e(BitmapHelper().toBase64(compressed))
44 | */
45 |
46 | if (data == null) {
47 | binding.txtRoadName.text = "---"
48 | binding.txtRoadAdditionalInfo.text = "---"
49 | binding.txtDistance.text = "---"
50 | binding.txtEta.text = "---"
51 | return
52 | }
53 |
54 | binding.txtRoadName.text = data.nextDirection.nextRoad
55 | binding.txtRoadAdditionalInfo.text = data.nextDirection.nextRoadAdditionalInfo
56 | binding.txtDistance.text = data.nextDirection.distance
57 | binding.txtEta.text = "${data.eta.ete} - ${data.eta.eta} - ${data.eta.distance}"
58 | }
59 |
60 | override fun onCreateView(
61 | inflater: LayoutInflater,
62 | container: ViewGroup?,
63 | savedInstanceState: Bundle?
64 | ): View {
65 | mUiBinding = FragmentHomeBinding.inflate(inflater, container, false)
66 |
67 | val viewModel = ViewModelProvider(requireActivity())[ActivityViewModel::class.java]
68 | viewModel.navigationData.observe(viewLifecycleOwner) {
69 | displayNavigationData(it)
70 | }
71 | viewModel.speed.observe(viewLifecycleOwner) {
72 | binding.txtSpeed.text = "$it km/h"
73 | }
74 |
75 | return binding.root
76 | }
77 |
78 | override fun onDestroyView() {
79 | super.onDestroyView()
80 | mUiBinding = null
81 | }
82 | }
--------------------------------------------------------------------------------
/cat-drive-android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 | @rem SPDX-License-Identifier: Apache-2.0
17 | @rem
18 |
19 | @if "%DEBUG%"=="" @echo off
20 | @rem ##########################################################################
21 | @rem
22 | @rem Gradle startup script for Windows
23 | @rem
24 | @rem ##########################################################################
25 |
26 | @rem Set local scope for the variables with windows NT shell
27 | if "%OS%"=="Windows_NT" setlocal
28 |
29 | set DIRNAME=%~dp0
30 | if "%DIRNAME%"=="" set DIRNAME=.
31 | @rem This is normally unused
32 | set APP_BASE_NAME=%~n0
33 | set APP_HOME=%DIRNAME%
34 |
35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
37 |
38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
40 |
41 | @rem Find java.exe
42 | if defined JAVA_HOME goto findJavaFromJavaHome
43 |
44 | set JAVA_EXE=java.exe
45 | %JAVA_EXE% -version >NUL 2>&1
46 | if %ERRORLEVEL% equ 0 goto execute
47 |
48 | echo. 1>&2
49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
50 | echo. 1>&2
51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
52 | echo location of your Java installation. 1>&2
53 |
54 | goto fail
55 |
56 | :findJavaFromJavaHome
57 | set JAVA_HOME=%JAVA_HOME:"=%
58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
59 |
60 | if exist "%JAVA_EXE%" goto execute
61 |
62 | echo. 1>&2
63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
64 | echo. 1>&2
65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
66 | echo location of your Java installation. 1>&2
67 |
68 | goto fail
69 |
70 | :execute
71 | @rem Setup the command line
72 |
73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
74 |
75 |
76 | @rem Execute Gradle
77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
78 |
79 | :end
80 | @rem End local scope for the variables with windows NT shell
81 | if %ERRORLEVEL% equ 0 goto mainEnd
82 |
83 | :fail
84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
85 | rem the _cmd.exe /c_ return code!
86 | set EXIT_CODE=%ERRORLEVEL%
87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
89 | exit /b %EXIT_CODE%
90 |
91 | :mainEnd
92 | if "%OS%"=="Windows_NT" endlocal
93 |
94 | :omega
95 |
--------------------------------------------------------------------------------
/cat-drive-esp32/splash.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | const unsigned char splash[] = {
4 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
5 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
6 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
7 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
8 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0x07, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00,
9 | 0x78, 0x03, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x03, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00,
10 | 0x78, 0x01, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x01, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00,
11 | 0x70, 0x01, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x01, 0xE0, 0x00, 0x00, 0x00, 0xF0, 0x00,
12 | 0x00, 0x01, 0xE0, 0x00, 0x00, 0x01, 0xF0, 0x00, 0x00, 0x01, 0xF0, 0x00, 0x00, 0x03, 0xF0, 0x00,
13 | 0x00, 0x00, 0xFC, 0xFF, 0xC0, 0x07, 0xF0, 0xE0, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x0F, 0xF3, 0xE0,
14 | 0x00, 0x00, 0x3F, 0xFF, 0xFE, 0x1F, 0xF7, 0xE0, 0x00, 0x00, 0x07, 0xBF, 0xFF, 0x1F, 0xFB, 0xE0,
15 | 0x00, 0x00, 0x3C, 0xFF, 0xFF, 0xDF, 0xFC, 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xEF, 0xFE, 0x40,
16 | 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xF7, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xF7, 0xFF, 0xC0,
17 | 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0xC0, 0x00, 0x03, 0xFF, 0x7F, 0xFF, 0xFD, 0xFF, 0xE0,
18 | 0x00, 0x03, 0xFE, 0x1F, 0xFF, 0xFD, 0x3F, 0xF0, 0x00, 0x07, 0xFC, 0x7F, 0xFF, 0xFE, 0x3F, 0xF0,
19 | 0x00, 0x07, 0xFE, 0xBF, 0xFF, 0xFE, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x98,
20 | 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0x79, 0x98, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0xF8,
21 | 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0,
22 | 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8,
23 | 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xDF, 0xE6, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xDF, 0xD0,
24 | 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xDF, 0xDF, 0x88, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xDF, 0xDE, 0x00,
25 | 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xDF, 0xC0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xDF, 0xC0, 0x00,
26 | 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xDF, 0xC0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xDE, 0xC0, 0x00,
27 | 0x00, 0x1F, 0xFF, 0xFF, 0x7F, 0xFE, 0x80, 0x00, 0x00, 0x0F, 0xBF, 0xFF, 0x7F, 0xBD, 0x80, 0x00,
28 | 0x00, 0x0F, 0xDF, 0xFF, 0x7F, 0xB9, 0x80, 0x00, 0x00, 0x0F, 0xE1, 0xFF, 0x7F, 0xB3, 0x80, 0x00,
29 | 0x00, 0x07, 0xE0, 0x00, 0xFF, 0x67, 0x00, 0x00, 0x00, 0x07, 0xF0, 0x00, 0xFF, 0x0F, 0x00, 0x00,
30 | 0x00, 0x07, 0xF0, 0x00, 0xFE, 0x1F, 0x00, 0x00, 0x00, 0x03, 0xF0, 0x00, 0xFE, 0x1F, 0x00, 0x00,
31 | 0x00, 0x03, 0xF8, 0x01, 0xFC, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
32 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
33 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
34 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
35 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
36 | };
37 |
--------------------------------------------------------------------------------
/cat-drive-esp32/pages/index.html.h:
--------------------------------------------------------------------------------
1 | const char* index_html =
2 | "\n"
3 | "\n"
4 | "\n"
5 | "\n"
6 | " \n"
7 | " \n"
8 | " \n"
9 | " CatDrive OTA Update\n"
10 | " \n"
15 | "\n"
16 | "\n"
17 | "\n"
18 | " CatDrive OTA Update
\n"
19 | " \n"
26 | " \n"
79 | "\n"
80 | "\n"
81 | "\n"
82 | "\n"
83 | ;
--------------------------------------------------------------------------------
/cat-drive-esp32/ota.cpp:
--------------------------------------------------------------------------------
1 | #ifdef USE_OTA
2 | #include "ota.h"
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | #include "pages/index.html.h"
10 |
11 | WebServer server(80);
12 | OtaUpdateCallback gUpdateCallback = nullptr;
13 | size_t gUploadedSize = 0;
14 | size_t gFileSize = 0;
15 | String gFileName;
16 | UpdateStatus gUpdateStatus = UpdateStatus::None;
17 |
18 | const char* redirect_html = "";
21 |
22 | void CatDriveOTA::begin(const char* host, const char* ssid, const char* password) {
23 | WiFi.mode(WIFI_AP_STA);
24 | WiFi.softAP(ssid, password);
25 |
26 | server.on("/", HTTP_GET, []() {
27 | server.sendHeader("Connection", "close");
28 | server.send(200, "text/html", redirect_html);
29 | });
30 |
31 | server.on("/main", HTTP_GET, []() {
32 | server.sendHeader("Connection", "close");
33 | server.send(200, "text/html", index_html);
34 | });
35 |
36 | server.on("/updateFileSize", HTTP_PUT, []() {
37 | if (server.hasArg("plain")) {
38 | gFileSize = server.arg("plain").toInt();
39 | }
40 | server.sendHeader("Connection", "close");
41 | server.send(200, "text/plain", "OK");
42 | });
43 |
44 | server.on(
45 | "/update", HTTP_POST, []() {
46 | server.sendHeader("Connection", "close");
47 | server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
48 | ESP.restart();
49 | },
50 | []() {
51 | HTTPUpload& upload = server.upload();
52 | UpdateStatus status;
53 |
54 | if (upload.status == UPLOAD_FILE_START) {
55 | Serial.printf("Update: %s\n", upload.filename.c_str());
56 | status = UpdateStatus::UpdateStart;
57 |
58 | if (!Update.begin(UPDATE_SIZE_UNKNOWN)) {
59 | Update.printError(Serial);
60 | status = UpdateStatus::UpdateFailed;
61 | }
62 | } else if (upload.status == UPLOAD_FILE_WRITE) {
63 | status = UpdateStatus::Updating;
64 | if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
65 | Update.printError(Serial);
66 | status = UpdateStatus::UpdateFailed;
67 | }
68 | } else if (upload.status == UPLOAD_FILE_END) {
69 | status = UpdateStatus::UpdateSuccedded;
70 | // true to set the size to the current progress
71 | if (Update.end(true)) {
72 | Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
73 | } else {
74 | Update.printError(Serial);
75 | status = UpdateStatus::UpdateFailed;
76 | }
77 | }
78 |
79 | gFileName = upload.filename;
80 | gUploadedSize = upload.totalSize;
81 | gUpdateStatus = status;
82 | if (gUpdateCallback)
83 | gUpdateCallback(status, upload.filename, upload.totalSize, gFileSize);
84 | });
85 | server.begin();
86 | }
87 |
88 | void CatDriveOTA::handle() {
89 | server.handleClient();
90 | }
91 |
92 | String CatDriveOTA::getAPIP() {
93 | return WiFi.softAPIP().toString();
94 | }
95 |
96 | UpdateStatus CatDriveOTA::status() {
97 | return UpdateStatus();
98 | }
99 |
100 | size_t CatDriveOTA::uploadedSize() {
101 | return gUploadedSize;
102 | }
103 |
104 | size_t CatDriveOTA::totalSize() {
105 | return gFileSize;
106 | }
107 |
108 | String CatDriveOTA::fileName() {
109 | return gFileName;
110 | }
111 |
112 | void CatDriveOTA::attachUpdateCallback(OtaUpdateCallback callback) {
113 | gUpdateCallback = callback;
114 | }
115 |
116 | #endif
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/res/layout/fragment_home.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
19 |
20 |
31 |
32 |
42 |
43 |
53 |
54 |
64 |
65 |
75 |
76 |
86 |
87 |
--------------------------------------------------------------------------------
/cat-drive-android/app/src/main/java/com/meomeo/catdrive/ui/DeviceSelectionActivity.kt:
--------------------------------------------------------------------------------
1 | package com.meomeo.catdrive.ui
2 |
3 | import android.annotation.SuppressLint
4 | import android.bluetooth.BluetoothDevice
5 | import android.bluetooth.BluetoothManager
6 | import android.content.Context
7 | import android.content.Intent
8 | import android.os.Bundle
9 | import android.os.Parcelable
10 | import android.view.LayoutInflater
11 | import android.view.View
12 | import android.view.ViewGroup
13 | import android.widget.Button
14 | import android.widget.FrameLayout
15 | import android.widget.TextView
16 | import androidx.appcompat.app.AppCompatActivity
17 | import androidx.recyclerview.widget.RecyclerView
18 | import com.meomeo.catdrive.R
19 | import com.meomeo.catdrive.utils.PermissionCheck
20 | import kotlinx.parcelize.Parcelize
21 | import kotlinx.serialization.Serializable
22 | import timber.log.Timber
23 |
24 | class CustomAdapter(onSelectCallback: (BluetoothDevice) -> Unit) : RecyclerView.Adapter() {
25 | private var mDataSet: List = mutableListOf()
26 | private val mOnSelectCallback = onSelectCallback
27 |
28 | var dataSet
29 | get() = mDataSet
30 | set(value) {
31 | if (value == mDataSet)
32 | return
33 | mDataSet = value
34 | notifyDataSetChanged()
35 | }
36 |
37 | class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
38 | val txtDeviceName: TextView
39 | val txtDeviceAddress: TextView
40 | val frame: FrameLayout
41 |
42 | init {
43 | txtDeviceName = view.findViewById(R.id.txtItemDeviceName)
44 | txtDeviceAddress = view.findViewById(R.id.txtItemMacAddress)
45 | frame = view.findViewById(R.id.frame)
46 | }
47 | }
48 |
49 | // Create new views (invoked by the layout manager)
50 | override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
51 | // Create a new view, which defines the UI of the list item
52 | val view = LayoutInflater.from(viewGroup.context)
53 | .inflate(R.layout.device_row_item, viewGroup, false)
54 |
55 | return ViewHolder(view)
56 | }
57 |
58 | // Replace the contents of a view (invoked by the layout manager)
59 | @SuppressLint("MissingPermission")
60 | override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
61 |
62 | // Get element from your dataset at this position and replace the
63 | // contents of the view with that element
64 | viewHolder.txtDeviceName.text = dataSet[position].name
65 | viewHolder.txtDeviceAddress.text = dataSet[position].address
66 |
67 | viewHolder.frame.setOnClickListener { _ -> mOnSelectCallback(dataSet[position]) }
68 | }
69 |
70 | // Return the size of your dataset (invoked by the layout manager)
71 | override fun getItemCount() = dataSet.size
72 | }
73 |
74 | class DeviceSelectionActivity : AppCompatActivity() {
75 | private lateinit var mViewDeviceAdapter: CustomAdapter
76 |
77 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
78 | super.onActivityResult(requestCode, resultCode, data)
79 | getDeviceList()
80 | }
81 |
82 | private fun onDeviceSelected(device: BluetoothDevice) {
83 | Timber.w(device.toString())
84 | setResult(RESULT_OK, Intent().apply { putExtra("device", device) })
85 | finish()
86 | }
87 |
88 | override fun onCreate(savedInstanceState: Bundle?) {
89 | super.onCreate(savedInstanceState)
90 | setContentView(R.layout.activity_device_selection)
91 |
92 | supportActionBar?.setDisplayHomeAsUpEnabled(true)
93 |
94 | mViewDeviceAdapter = CustomAdapter(this::onDeviceSelected)
95 | findViewById(R.id.viewDeviceList).apply { adapter = mViewDeviceAdapter }
96 | findViewById