田子悦 4 days ago
parent 87c22f77e6
commit ad929e15b1

BIN
.DS_Store vendored

Binary file not shown.

Binary file not shown.

@ -0,0 +1,703 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DeviceStreaming">
<option name="deviceSelectionList">
<list>
<PersistentDeviceSelectionData>
<option name="api" value="27" />
<option name="brand" value="DOCOMO" />
<option name="codename" value="F01L" />
<option name="id" value="F01L" />
<option name="labId" value="google" />
<option name="manufacturer" value="FUJITSU" />
<option name="name" value="F-01L" />
<option name="screenDensity" value="360" />
<option name="screenX" value="720" />
<option name="screenY" value="1280" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="OnePlus" />
<option name="codename" value="OP535DL1" />
<option name="id" value="OP535DL1" />
<option name="labId" value="google" />
<option name="manufacturer" value="OnePlus" />
<option name="name" value="CPH2409" />
<option name="screenDensity" value="401" />
<option name="screenX" value="1080" />
<option name="screenY" value="2412" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="OnePlus" />
<option name="codename" value="OP5552L1" />
<option name="id" value="OP5552L1" />
<option name="labId" value="google" />
<option name="manufacturer" value="OnePlus" />
<option name="name" value="CPH2415" />
<option name="screenDensity" value="480" />
<option name="screenX" value="1080" />
<option name="screenY" value="2412" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="OPPO" />
<option name="codename" value="OP573DL1" />
<option name="id" value="OP573DL1" />
<option name="labId" value="google" />
<option name="manufacturer" value="OPPO" />
<option name="name" value="CPH2557" />
<option name="screenDensity" value="480" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="28" />
<option name="brand" value="DOCOMO" />
<option name="codename" value="SH-01L" />
<option name="id" value="SH-01L" />
<option name="labId" value="google" />
<option name="manufacturer" value="SHARP" />
<option name="name" value="AQUOS sense2 SH-01L" />
<option name="screenDensity" value="480" />
<option name="screenX" value="1080" />
<option name="screenY" value="2160" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="a15" />
<option name="id" value="a15" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="A15" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="a15x" />
<option name="id" value="a15x" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="A15 5G" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="a16x" />
<option name="id" value="a16x" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="A16 5G" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="a35x" />
<option name="id" value="a35x" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="A35" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="31" />
<option name="brand" value="samsung" />
<option name="codename" value="a51" />
<option name="id" value="a51" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy A51" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="akita" />
<option name="id" value="akita" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 8a" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="motorola" />
<option name="codename" value="arcfox" />
<option name="id" value="arcfox" />
<option name="labId" value="google" />
<option name="manufacturer" value="Motorola" />
<option name="name" value="razr plus 2024" />
<option name="screenDensity" value="360" />
<option name="screenX" value="1080" />
<option name="screenY" value="1272" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="motorola" />
<option name="codename" value="austin" />
<option name="id" value="austin" />
<option name="labId" value="google" />
<option name="manufacturer" value="Motorola" />
<option name="name" value="moto g 5G (2022)" />
<option name="screenDensity" value="280" />
<option name="screenX" value="720" />
<option name="screenY" value="1600" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="samsung" />
<option name="codename" value="b0q" />
<option name="id" value="b0q" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S22 Ultra" />
<option name="screenDensity" value="600" />
<option name="screenX" value="1440" />
<option name="screenY" value="3088" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="b6q" />
<option name="id" value="b6q" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Flip 6" />
<option name="screenDensity" value="340" />
<option name="screenX" value="1080" />
<option name="screenY" value="2640" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="32" />
<option name="brand" value="google" />
<option name="codename" value="bluejay" />
<option name="id" value="bluejay" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 6a" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="caiman" />
<option name="id" value="caiman" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 9 Pro" />
<option name="screenDensity" value="360" />
<option name="screenX" value="960" />
<option name="screenY" value="2142" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="comet" />
<option name="default" value="true" />
<option name="id" value="comet" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 9 Pro Fold" />
<option name="screenDensity" value="390" />
<option name="screenX" value="2076" />
<option name="screenY" value="2152" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="35" />
<option name="brand" value="google" />
<option name="codename" value="comet" />
<option name="default" value="true" />
<option name="id" value="comet" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 9 Pro Fold" />
<option name="screenDensity" value="390" />
<option name="screenX" value="2076" />
<option name="screenY" value="2152" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="29" />
<option name="brand" value="samsung" />
<option name="codename" value="crownqlteue" />
<option name="id" value="crownqlteue" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Note9" />
<option name="screenDensity" value="420" />
<option name="screenX" value="2220" />
<option name="screenY" value="1080" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="dm2q" />
<option name="id" value="dm2q" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="S23 Plus" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="dm3q" />
<option name="id" value="dm3q" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S23 Ultra" />
<option name="screenDensity" value="600" />
<option name="screenX" value="1440" />
<option name="screenY" value="3088" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="e1q" />
<option name="default" value="true" />
<option name="id" value="e1q" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S24" />
<option name="screenDensity" value="480" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="e3q" />
<option name="id" value="e3q" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S24 Ultra" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1440" />
<option name="screenY" value="3120" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="eos" />
<option name="id" value="eos" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
<option name="name" value="Eos" />
<option name="screenDensity" value="320" />
<option name="screenX" value="384" />
<option name="screenY" value="384" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="felix" />
<option name="id" value="felix" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel Fold" />
<option name="screenDensity" value="420" />
<option name="screenX" value="2208" />
<option name="screenY" value="1840" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="felix" />
<option name="id" value="felix" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel Fold" />
<option name="screenDensity" value="420" />
<option name="screenX" value="2208" />
<option name="screenY" value="1840" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="felix_camera" />
<option name="id" value="felix_camera" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel Fold (Camera-enabled)" />
<option name="screenDensity" value="420" />
<option name="screenX" value="2208" />
<option name="screenY" value="1840" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="motorola" />
<option name="codename" value="fogona" />
<option name="id" value="fogona" />
<option name="labId" value="google" />
<option name="manufacturer" value="Motorola" />
<option name="name" value="moto g play - 2024" />
<option name="screenDensity" value="280" />
<option name="screenX" value="720" />
<option name="screenY" value="1600" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="g0q" />
<option name="id" value="g0q" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="SM-S906U1" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="gta9pwifi" />
<option name="id" value="gta9pwifi" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="SM-X210" />
<option name="screenDensity" value="240" />
<option name="screenX" value="1200" />
<option name="screenY" value="1920" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="gts7xllite" />
<option name="id" value="gts7xllite" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="SM-T738U" />
<option name="screenDensity" value="340" />
<option name="screenX" value="1600" />
<option name="screenY" value="2560" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="samsung" />
<option name="codename" value="gts8uwifi" />
<option name="formFactor" value="Tablet" />
<option name="id" value="gts8uwifi" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Tab S8 Ultra" />
<option name="screenDensity" value="320" />
<option name="screenX" value="1848" />
<option name="screenY" value="2960" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="gts8wifi" />
<option name="formFactor" value="Tablet" />
<option name="id" value="gts8wifi" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Tab S8" />
<option name="screenDensity" value="274" />
<option name="screenX" value="1600" />
<option name="screenY" value="2560" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="gts9fe" />
<option name="id" value="gts9fe" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Tab S9 FE 5G" />
<option name="screenDensity" value="280" />
<option name="screenX" value="1440" />
<option name="screenY" value="2304" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="gts9wifi" />
<option name="id" value="gts9wifi" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="SM-X710" />
<option name="screenDensity" value="340" />
<option name="screenX" value="1600" />
<option name="screenY" value="2560" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="husky" />
<option name="id" value="husky" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 8 Pro" />
<option name="screenDensity" value="390" />
<option name="screenX" value="1008" />
<option name="screenY" value="2244" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="30" />
<option name="brand" value="motorola" />
<option name="codename" value="java" />
<option name="id" value="java" />
<option name="labId" value="google" />
<option name="manufacturer" value="Motorola" />
<option name="name" value="G20" />
<option name="screenDensity" value="280" />
<option name="screenX" value="720" />
<option name="screenY" value="1600" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="komodo" />
<option name="id" value="komodo" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 9 Pro XL" />
<option name="screenDensity" value="360" />
<option name="screenX" value="1008" />
<option name="screenY" value="2244" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="35" />
<option name="brand" value="google" />
<option name="codename" value="komodo" />
<option name="id" value="komodo" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 9 Pro XL" />
<option name="screenDensity" value="360" />
<option name="screenX" value="1008" />
<option name="screenY" value="2244" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="lynx" />
<option name="id" value="lynx" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 7a" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="motorola" />
<option name="codename" value="maui" />
<option name="id" value="maui" />
<option name="labId" value="google" />
<option name="manufacturer" value="Motorola" />
<option name="name" value="moto g play - 2023" />
<option name="screenDensity" value="280" />
<option name="screenX" value="720" />
<option name="screenY" value="1600" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="o1q" />
<option name="id" value="o1q" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S21" />
<option name="screenDensity" value="421" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="31" />
<option name="brand" value="google" />
<option name="codename" value="oriole" />
<option name="id" value="oriole" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 6" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="panther" />
<option name="id" value="panther" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 7" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="q5q" />
<option name="id" value="q5q" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Z Fold5" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1812" />
<option name="screenY" value="2176" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="q6q" />
<option name="id" value="q6q" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Z Fold6" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1856" />
<option name="screenY" value="2160" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="30" />
<option name="brand" value="google" />
<option name="codename" value="r11" />
<option name="formFactor" value="Wear OS" />
<option name="id" value="r11" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel Watch" />
<option name="screenDensity" value="320" />
<option name="screenX" value="384" />
<option name="screenY" value="384" />
<option name="type" value="WEAR_OS" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="r11q" />
<option name="id" value="r11q" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="SM-S711U" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="30" />
<option name="brand" value="google" />
<option name="codename" value="redfin" />
<option name="id" value="redfin" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 5" />
<option name="screenDensity" value="440" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="shiba" />
<option name="id" value="shiba" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 8" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="t2q" />
<option name="id" value="t2q" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S21 Plus" />
<option name="screenDensity" value="394" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="tangorpro" />
<option name="formFactor" value="Tablet" />
<option name="id" value="tangorpro" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel Tablet" />
<option name="screenDensity" value="320" />
<option name="screenX" value="1600" />
<option name="screenY" value="2560" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="35" />
<option name="brand" value="google" />
<option name="codename" value="tegu" />
<option name="id" value="tegu" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 9a" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2424" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="tokay" />
<option name="default" value="true" />
<option name="id" value="tokay" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 9" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2424" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="35" />
<option name="brand" value="google" />
<option name="codename" value="tokay" />
<option name="default" value="true" />
<option name="id" value="tokay" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 9" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2424" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="xcover7" />
<option name="id" value="xcover7" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="SM-G556B" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2408" />
</PersistentDeviceSelectionData>
</list>
</option>
</component>
</project>

@ -4,15 +4,14 @@
<selectionStates> <selectionStates>
<SelectionState runConfigName="app"> <SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
</SelectionState> <DropdownSelection timestamp="2025-06-03T12:40:50.122129Z">
<SelectionState runConfigName="MovenetLightningTest"> <Target type="DEFAULT_BOOT">
<option name="selectionMode" value="DROPDOWN" /> <handle>
</SelectionState> <DeviceId pluginId="PhysicalDevice" identifier="serial=YPL0223227003230" />
<SelectionState runConfigName="MovenetThunderTest"> </handle>
<option name="selectionMode" value="DROPDOWN" /> </Target>
</SelectionState> </DropdownSelection>
<SelectionState runConfigName="MovenetMultiPoseTest"> <DialogSelection />
<option name="selectionMode" value="DROPDOWN" />
</SelectionState> </SelectionState>
</selectionStates> </selectionStates>
</component> </component>

@ -1,10 +1,11 @@
plugins { plugins {
id 'com.android.application' id 'com.android.application'
id 'kotlin-android' id 'kotlin-android'
id 'kotlin-kapt'
} }
android { android {
compileSdkVersion 30 compileSdkVersion 31
buildToolsVersion "30.0.3" buildToolsVersion "30.0.3"
defaultConfig { defaultConfig {
@ -50,6 +51,12 @@ dependencies {
implementation 'org.tensorflow:tensorflow-lite-gpu:2.5.0' implementation 'org.tensorflow:tensorflow-lite-gpu:2.5.0'
implementation 'org.tensorflow:tensorflow-lite-support:0.3.0' implementation 'org.tensorflow:tensorflow-lite-support:0.3.0'
// Room
def room_version = "2.4.3"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version"
androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
androidTestImplementation "com.google.truth:truth:1.1.3" androidTestImplementation "com.google.truth:truth:1.1.3"

@ -19,6 +19,7 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name=".MainTabActivity" />
<activity android:name=".MainActivity" /> <activity android:name=".MainActivity" />
<activity android:name=".GenderSelectionActivity" /> <activity android:name=".GenderSelectionActivity" />
<activity android:name=".AgeSelectionActivity" /> <activity android:name=".AgeSelectionActivity" />
@ -36,6 +37,7 @@
<activity <activity
android:name=".SignupActivity" android:name=".SignupActivity"
android:exported="false" /> android:exported="false" />
<activity android:name=".ExerciseDetailActivity" />
</application> </application>
</manifest> </manifest>

@ -0,0 +1,17 @@
package org.tensorflow.lite.examples.poseestimation
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import org.tensorflow.lite.examples.poseestimation.R
class DataFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_data, container, false)
}
}

@ -0,0 +1,55 @@
package org.tensorflow.lite.examples.poseestimation
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView
import android.widget.Button
class ExerciseDetailActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_exercise_detail)
// 找到布局中的控件
val backButton = findViewById<ImageButton>(R.id.back_button)
val exerciseImage = findViewById<ImageView>(R.id.exercise_detail_image)
val exerciseName = findViewById<TextView>(R.id.exercise_detail_name)
val exerciseDescription = findViewById<TextView>(R.id.exercise_detail_description)
val startTrainingButton = findViewById<Button>(R.id.start_training_button)
// 获取从 HomeFragment 传递过来的动作名称
val exerciseNameFromIntent = intent.getStringExtra("exercise_name")
// 设置页面内容
exerciseNameFromIntent?.let { name ->
exerciseName.text = name
// 根据动作名称设置对应的图片
val imageResId = when (name) {
"硬拉" -> R.drawable.deadlift
"深蹲" -> R.drawable.deep_squats
"平板支撑" -> R.drawable.plank
"引体向上" -> R.drawable.pull_up
"俯卧撑" -> R.drawable.push_up
else -> R.drawable.placeholder_image
}
exerciseImage.setImageResource(imageResId)
}
// 设置返回按钮点击事件
backButton.setOnClickListener {
onBackPressed() // 返回上一个Activity
}
// 设置开始训练按钮点击事件,跳转到姿态识别页面
startTrainingButton.setOnClickListener {
val intent = Intent(this, MainActivity::class.java)
// 传递当前动作名称给 MainActivity
intent.putExtra("current_exercise", exerciseNameFromIntent)
startActivity(intent)
}
}
}

@ -8,6 +8,11 @@ import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import kotlinx.coroutines.Dispatchers
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import org.tensorflow.lite.examples.poseestimation.data.AppDatabase
import org.tensorflow.lite.examples.poseestimation.data.UserProfile
import kotlin.math.abs import kotlin.math.abs
class HeightSelectionActivity : AppCompatActivity() { class HeightSelectionActivity : AppCompatActivity() {
@ -22,6 +27,7 @@ class HeightSelectionActivity : AppCompatActivity() {
private var selectedGender: String? = null private var selectedGender: String? = null
private var selectedAge: Int = 0 private var selectedAge: Int = 0
private var selectedWeight: Int = 0 private var selectedWeight: Int = 0
private var username: String? = null
private var currentHeight = 167 private var currentHeight = 167
private val minHeight = 100 private val minHeight = 100
@ -37,6 +43,7 @@ class HeightSelectionActivity : AppCompatActivity() {
selectedGender = intent.getStringExtra("selected_gender") selectedGender = intent.getStringExtra("selected_gender")
selectedAge = intent.getIntExtra("selected_age", 0) selectedAge = intent.getIntExtra("selected_age", 0)
selectedWeight = intent.getIntExtra("selected_weight", 0) selectedWeight = intent.getIntExtra("selected_weight", 0)
username = intent.getStringExtra("username")
selectedHeightText = findViewById(R.id.selectedHeightText) selectedHeightText = findViewById(R.id.selectedHeightText)
heightUnit = findViewById(R.id.heightUnit) heightUnit = findViewById(R.id.heightUnit)
@ -84,11 +91,25 @@ class HeightSelectionActivity : AppCompatActivity() {
private fun setupClickListeners() { private fun setupClickListeners() {
nextButton.setOnClickListener { nextButton.setOnClickListener {
// 保存用户个人信息到数据库
val db = AppDatabase.getDatabase(this)
val gender = selectedGender ?: ""
val age = selectedAge
val weight = selectedWeight
val height = currentHeight
val user = username ?: ""
lifecycleScope.launch(Dispatchers.IO) {
try {
val profile = UserProfile(user, gender, age, weight, height)
db.userProfileDao().insertUserProfile(profile)
} catch (_: Exception) {}
}
val intent = Intent(this, LoginActivity::class.java) val intent = Intent(this, LoginActivity::class.java)
intent.putExtra("selected_gender", selectedGender) intent.putExtra("selected_gender", selectedGender)
intent.putExtra("selected_age", selectedAge) intent.putExtra("selected_age", selectedAge)
intent.putExtra("selected_weight", selectedWeight) intent.putExtra("selected_weight", selectedWeight)
intent.putExtra("selected_height", currentHeight) intent.putExtra("selected_height", currentHeight)
intent.putExtra("username", username)
startActivity(intent) startActivity(intent)
finish() finish()
} }
@ -98,6 +119,7 @@ class HeightSelectionActivity : AppCompatActivity() {
intent.putExtra("selected_gender", selectedGender) intent.putExtra("selected_gender", selectedGender)
intent.putExtra("selected_age", selectedAge) intent.putExtra("selected_age", selectedAge)
intent.putExtra("selected_weight", selectedWeight) intent.putExtra("selected_weight", selectedWeight)
intent.putExtra("username", username)
startActivity(intent) startActivity(intent)
finish() finish()
} }

@ -0,0 +1,80 @@
package org.tensorflow.lite.examples.poseestimation
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.fragment.app.Fragment
import org.tensorflow.lite.examples.poseestimation.R
class HomeFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_home, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 设置硬拉卡片
val deadliftCard = view.findViewById<View>(R.id.deadlift_card)
val deadliftImage = deadliftCard.findViewById<ImageView>(R.id.exercise_image)
val deadliftName = deadliftCard.findViewById<TextView>(R.id.exercise_name)
deadliftImage.setImageResource(R.drawable.deadlift)
deadliftName.text = "硬拉"
deadliftCard.setOnClickListener {
startExerciseDetail("硬拉")
}
// 设置深蹲卡片
val squatCard = view.findViewById<View>(R.id.squat_card)
val squatImage = squatCard.findViewById<ImageView>(R.id.exercise_image)
val squatName = squatCard.findViewById<TextView>(R.id.exercise_name)
squatImage.setImageResource(R.drawable.deep_squats)
squatName.text = "深蹲"
squatCard.setOnClickListener {
startExerciseDetail("深蹲")
}
// 设置平板支撑卡片
val plankCard = view.findViewById<View>(R.id.plank_card)
val plankImage = plankCard.findViewById<ImageView>(R.id.exercise_image)
val plankName = plankCard.findViewById<TextView>(R.id.exercise_name)
plankImage.setImageResource(R.drawable.plank)
plankName.text = "平板支撑"
plankCard.setOnClickListener {
startExerciseDetail("平板支撑")
}
// 设置引体向上卡片
val pullupCard = view.findViewById<View>(R.id.pullup_card)
val pullupImage = pullupCard.findViewById<ImageView>(R.id.exercise_image)
val pullupName = pullupCard.findViewById<TextView>(R.id.exercise_name)
pullupImage.setImageResource(R.drawable.pull_up)
pullupName.text = "引体向上"
pullupCard.setOnClickListener {
startExerciseDetail("引体向上")
}
// 设置俯卧撑卡片
val pushupCard = view.findViewById<View>(R.id.pushup_card)
val pushupImage = pushupCard.findViewById<ImageView>(R.id.exercise_image)
val pushupName = pushupCard.findViewById<TextView>(R.id.exercise_name)
pushupImage.setImageResource(R.drawable.push_up)
pushupName.text = "俯卧撑"
pushupCard.setOnClickListener {
startExerciseDetail("俯卧撑")
}
}
private fun startExerciseDetail(exerciseName: String) {
val intent = Intent(requireContext(), ExerciseDetailActivity::class.java)
intent.putExtra("exercise_name", exerciseName)
startActivity(intent)
}
}

@ -4,10 +4,13 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.textfield.TextInputEditText
import android.widget.Button import android.widget.Button
import android.widget.TextView import android.widget.TextView
import android.widget.EditText import android.widget.EditText
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import org.tensorflow.lite.examples.poseestimation.data.AppDatabase
class LoginActivity : AppCompatActivity() { class LoginActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -27,13 +30,16 @@ class LoginActivity : AppCompatActivity() {
val forgotPassword = findViewById<TextView>(R.id.forgotPassword) val forgotPassword = findViewById<TextView>(R.id.forgotPassword)
val signupTab = findViewById<TextView>(R.id.tabSignup) val signupTab = findViewById<TextView>(R.id.tabSignup)
// 获取数据库实例
val db = AppDatabase.getDatabase(this)
// 登录按钮点击事件 // 登录按钮点击事件
loginBtn.setOnClickListener { loginBtn.setOnClickListener {
val email = emailEdit.text.toString().trim() val username = emailEdit.text.toString().trim()
val password = passwordEdit.text.toString().trim() val password = passwordEdit.text.toString().trim()
// 表单验证 // 表单验证
if (email.isEmpty()) { if (username.isEmpty()) {
emailEdit.error = "请输入用户名" emailEdit.error = "请输入用户名"
return@setOnClickListener return@setOnClickListener
} }
@ -42,15 +48,30 @@ class LoginActivity : AppCompatActivity() {
return@setOnClickListener return@setOnClickListener
} }
// TODO: 这里添加实际的登录验证逻辑 // 在协程中执行数据库操作
// 目前仅做演示,直接跳转到主页面 lifecycleScope.launch {
val intent = Intent(this, MainActivity::class.java) try {
val user = db.userDao().getUser(username, password).first()
if (user != null) {
// 登录成功
val intent = Intent(this@LoginActivity, org.tensorflow.lite.examples.poseestimation.MainTabActivity::class.java)
intent.putExtra("selected_gender", selectedGender) intent.putExtra("selected_gender", selectedGender)
intent.putExtra("selected_age", selectedAge) intent.putExtra("selected_age", selectedAge)
intent.putExtra("selected_weight", selectedWeight) intent.putExtra("selected_weight", selectedWeight)
intent.putExtra("selected_height", selectedHeight) intent.putExtra("selected_height", selectedHeight)
startActivity(intent) startActivity(intent)
finish() finish()
} else {
runOnUiThread {
Toast.makeText(this@LoginActivity, "用户名或密码错误", Toast.LENGTH_SHORT).show()
}
}
} catch (e: Exception) {
runOnUiThread {
Toast.makeText(this@LoginActivity, "登录失败:${e.message}", Toast.LENGTH_SHORT).show()
}
}
}
} }
// 忘记密码点击事件 // 忘记密码点击事件

@ -48,6 +48,9 @@ import org.tensorflow.lite.examples.poseestimation.ml.PoseClassifier
import org.tensorflow.lite.examples.poseestimation.ml.PoseNet import org.tensorflow.lite.examples.poseestimation.ml.PoseNet
import org.tensorflow.lite.examples.poseestimation.ml.TrackerType import org.tensorflow.lite.examples.poseestimation.ml.TrackerType
import org.tensorflow.lite.examples.poseestimation.ml.Type import org.tensorflow.lite.examples.poseestimation.ml.Type
import org.tensorflow.lite.examples.poseestimation.data.Angle
import org.tensorflow.lite.examples.poseestimation.data.AngleDifference
import org.tensorflow.lite.examples.poseestimation.data.Person
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
companion object { companion object {
@ -141,11 +144,17 @@ class MainActivity : AppCompatActivity() {
isPoseClassifier() isPoseClassifier()
} }
private lateinit var tvPoseAdvice: TextView
private var currentExerciseType: String? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
// keep screen on while app is running // keep screen on while app is running
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
currentExerciseType = intent.getStringExtra("current_exercise")
tvScore = findViewById(R.id.tvScore) tvScore = findViewById(R.id.tvScore)
tvFPS = findViewById(R.id.tvFps) tvFPS = findViewById(R.id.tvFps)
spnModel = findViewById(R.id.spnModel) spnModel = findViewById(R.id.spnModel)
@ -158,6 +167,8 @@ class MainActivity : AppCompatActivity() {
tvClassificationValue3 = findViewById(R.id.tvClassificationValue3) tvClassificationValue3 = findViewById(R.id.tvClassificationValue3)
swClassification = findViewById(R.id.swPoseClassification) swClassification = findViewById(R.id.swPoseClassification)
vClassificationOption = findViewById(R.id.vClassificationOption) vClassificationOption = findViewById(R.id.vClassificationOption)
tvPoseAdvice = findViewById(R.id.tv_pose_advice)
initSpinner() initSpinner()
spnModel.setSelection(modelPos) spnModel.setSelection(modelPos)
swClassification.setOnCheckedChangeListener(setClassificationListener) swClassification.setOnCheckedChangeListener(setClassificationListener)
@ -203,7 +214,8 @@ class MainActivity : AppCompatActivity() {
override fun onDetectedInfo( override fun onDetectedInfo(
personScore: Float?, personScore: Float?,
poseLabels: List<Pair<String, Float>>? poseLabels: List<Pair<String, Float>>?,
persons: List<Person>
) { ) {
tvScore.text = getString(R.string.tfe_pe_tv_score, personScore ?: 0f) tvScore.text = getString(R.string.tfe_pe_tv_score, personScore ?: 0f)
poseLabels?.sortedByDescending { it.second }?.let { poseLabels?.sortedByDescending { it.second }?.let {
@ -220,6 +232,29 @@ class MainActivity : AppCompatActivity() {
convertPoseLabels(if (it.size >= 3) it[2] else null) convertPoseLabels(if (it.size >= 3) it[2] else null)
) )
} }
if (persons.isNotEmpty() && currentExerciseType != null) {
val angleObj = Angle()
val angleDifference = AngleDifference(angleObj)
val personKeyPoints = persons[0].keyPoints
val advice = when (currentExerciseType) {
"硬拉" -> angleDifference.calculateAngleDifference(personKeyPoints, "2")
"深蹲" -> angleDifference.calculateAngleDifference(personKeyPoints, "4")
"平板支撑" -> angleDifference.calculateAngleDifference(personKeyPoints, "5")
"引体向上" -> angleDifference.calculateAngleDifference(personKeyPoints, "1")
"俯卧撑" -> angleDifference.calculateAngleDifference(personKeyPoints, "3")
else -> "未知动作类型,无法给出建议"
}
runOnUiThread {
tvPoseAdvice.text = advice
}
} else if (currentExerciseType == null) {
runOnUiThread {
tvPoseAdvice.text = "未指定动作类型,无法给出建议"
}
}
} }
}).apply { }).apply {

@ -0,0 +1,72 @@
package org.tensorflow.lite.examples.poseestimation
import android.os.Bundle
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import org.tensorflow.lite.examples.poseestimation.R
class MainTabActivity : AppCompatActivity() {
private lateinit var navHome: ImageView
private lateinit var navData: ImageView
private lateinit var navSetting: ImageView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main_tab)
navHome = findViewById(R.id.nav_home)
navData = findViewById(R.id.nav_data)
navSetting = findViewById(R.id.nav_setting)
// 底部导航栏跳转逻辑:
// 1. 在 onCreate 中初始化三个导航按钮navHome、navData、navSetting
// 2. 默认显示 HomeFragment并更新导航图标
// 3. 为每个导航按钮设置点击事件,点击时调用 switchFragment 切换对应的 Fragment并调用 updateNavIcons 更新图标状态
// 4. switchFragment 方法使用 supportFragmentManager 的 beginTransaction 替换 fragment_container 中的 Fragment
// 5. updateNavIcons 方法根据当前选中的导航项0、1、2更新三个导航按钮的图标资源
// 默认显示HomeFragment
switchFragment(HomeFragment())
updateNavIcons(0)
navHome.setOnClickListener {
switchFragment(HomeFragment())
updateNavIcons(0)
}
navData.setOnClickListener {
switchFragment(DataFragment())
updateNavIcons(1)
}
navSetting.setOnClickListener {
switchFragment(SettingFragment())
updateNavIcons(2)
}
}
private fun switchFragment(fragment: Fragment) {
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, fragment)
.commit()
}
private fun updateNavIcons(selected: Int) {
when (selected) {
0 -> {
navHome.setImageResource(R.drawable.home1)
navData.setImageResource(R.drawable.data2)
navSetting.setImageResource(R.drawable.setting2)
}
1 -> {
navHome.setImageResource(R.drawable.home2)
navData.setImageResource(R.drawable.data1)
navSetting.setImageResource(R.drawable.setting2)
}
2 -> {
navHome.setImageResource(R.drawable.home2)
navData.setImageResource(R.drawable.data2)
navSetting.setImageResource(R.drawable.setting1)
}
}
}
}

@ -0,0 +1,17 @@
package org.tensorflow.lite.examples.poseestimation
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import org.tensorflow.lite.examples.poseestimation.R
class SettingFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_setting, container, false)
}
}

@ -1,11 +1,18 @@
package org.tensorflow.lite.examples.poseestimation package org.tensorflow.lite.examples.poseestimation
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.widget.Button import android.widget.Button
import android.widget.EditText import android.widget.EditText
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.Dispatchers
import org.tensorflow.lite.examples.poseestimation.data.AppDatabase
import org.tensorflow.lite.examples.poseestimation.data.User
class SignupActivity : AppCompatActivity() { class SignupActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -19,6 +26,9 @@ class SignupActivity : AppCompatActivity() {
val signupBtn = findViewById<Button>(R.id.btnSignup) val signupBtn = findViewById<Button>(R.id.btnSignup)
val loginTab = findViewById<TextView>(R.id.tabLogin) val loginTab = findViewById<TextView>(R.id.tabLogin)
// 获取数据库实例
val db = AppDatabase.getDatabase(this)
// 注册按钮点击事件 // 注册按钮点击事件
signupBtn.setOnClickListener { signupBtn.setOnClickListener {
val username = usernameEdit.text.toString().trim() val username = usernameEdit.text.toString().trim()
@ -46,9 +56,35 @@ class SignupActivity : AppCompatActivity() {
return@setOnClickListener return@setOnClickListener
} }
// TODO: 这里添加实际的注册逻辑 // 在IO线程中执行数据库操作
Toast.makeText(this, "注册成功", Toast.LENGTH_SHORT).show() lifecycleScope.launch(Dispatchers.IO) {
finish() try {
// 检查用户名是否已存在
val existingUser = db.userDao().getUserByUsername(username).first()
if (existingUser != null) {
runOnUiThread {
Toast.makeText(this@SignupActivity, "用户名已存在", Toast.LENGTH_SHORT).show()
}
return@launch
}
// 创建新用户
val newUser = User(username, password)
db.userDao().insertUser(newUser)
runOnUiThread {
Toast.makeText(this@SignupActivity, "注册成功", Toast.LENGTH_SHORT).show()
// 注册成功后跳转到OnboardingActivity
val intent = Intent(this@SignupActivity, OnboardingActivity::class.java)
startActivity(intent)
finish() // 结束注册界面
}
} catch (e: Exception) {
runOnUiThread {
Toast.makeText(this@SignupActivity, "注册失败:${e.message}", Toast.LENGTH_SHORT).show()
}
}
}
} }
// Login Tab点击事件 // Login Tab点击事件

@ -41,9 +41,9 @@ class SplashActivity : AppCompatActivity() {
vector2.layoutParams = params2 vector2.layoutParams = params2
} }
// 2秒后跳转到引导 // 2秒后跳转到登录
Handler(Looper.getMainLooper()).postDelayed({ Handler(Looper.getMainLooper()).postDelayed({
startActivity(Intent(this, OnboardingActivity::class.java)) startActivity(Intent(this, LoginActivity::class.java))
finish() finish()
}, 2000) }, 2000)
} }

@ -264,7 +264,7 @@ class CameraSource(
// if the model returns only one item, show that item's score. // if the model returns only one item, show that item's score.
if (persons.isNotEmpty()) { if (persons.isNotEmpty()) {
listener?.onDetectedInfo(persons[0].score, classificationResult) listener?.onDetectedInfo(persons[0].score, classificationResult, persons)
} }
visualize(persons, bitmap) visualize(persons, bitmap)
} }
@ -322,6 +322,10 @@ class CameraSource(
interface CameraSourceListener { interface CameraSourceListener {
fun onFPSListener(fps: Int) fun onFPSListener(fps: Int)
fun onDetectedInfo(personScore: Float?, poseLabels: List<Pair<String, Float>>?) fun onDetectedInfo(
personScore: Float?,
poseLabels: List<Pair<String, Float>>?,
persons: List<Person>
)
} }
} }

@ -0,0 +1,31 @@
package org.tensorflow.lite.examples.poseestimation.data
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
@Database(entities = [User::class, UserProfile::class], version = 2, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
abstract fun userProfileDao(): UserProfileDao
companion object {
@Volatile
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"app_database"
)
.fallbackToDestructiveMigration()
.build()
INSTANCE = instance
instance
}
}
}
}

@ -0,0 +1,11 @@
package org.tensorflow.lite.examples.poseestimation.data
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "users")
data class User(
@PrimaryKey
val username: String,
val password: String
)

@ -0,0 +1,18 @@
package org.tensorflow.lite.examples.poseestimation.data
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import kotlinx.coroutines.flow.Flow
@Dao
interface UserDao {
@Insert
fun insertUser(user: User)
@Query("SELECT * FROM users WHERE username = :username AND password = :password")
fun getUser(username: String, password: String): Flow<User?>
@Query("SELECT * FROM users WHERE username = :username")
fun getUserByUsername(username: String): Flow<User?>
}

@ -0,0 +1,13 @@
package org.tensorflow.lite.examples.poseestimation.data
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "user_profiles")
data class UserProfile(
@PrimaryKey val username: String, // 与User表关联
val gender: String,
val age: Int,
val weight: Int,
val height: Int
)

@ -0,0 +1,15 @@
package org.tensorflow.lite.examples.poseestimation.data
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import kotlinx.coroutines.flow.Flow
@Dao
interface UserProfileDao {
@Insert
fun insertUserProfile(profile: UserProfile)
@Query("SELECT * FROM user_profiles WHERE username = :username")
fun getUserProfileByUsername(username: String): Flow<UserProfile?>
}

@ -1,5 +1,10 @@
package org.tensorflow.lite.examples.poseestimation.data package org.tensorflow.lite.examples.poseestimation.data
import android.graphics.PointF
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.acos
import kotlin.math.atan2
import kotlin.math.sqrt
class Angle { class Angle {
@ -72,9 +77,37 @@ class Angle {
class AngleDifference(private val angleObj: Angle) { class AngleDifference(private val angleObj: Angle) {
// 假设getAngles()方法已经实现并能返回一个List<Double> // 根据关键点计算关节角度
fun getAngles(): List<Double> { fun getAngles(keyPoints: List<KeyPoint>): List<Double> {
return listOf(100.0, 95.0, 180.0, 85.0, 90.0, 75.0, 160.0, 170.0) val angles = mutableListOf<Double>()
// 定义需要计算角度的关节及其组成关键点
// 顺序对应 BodyParts: 左肩, 右肩, 左肘, 右肘, 左髋, 右髋, 左膝, 右膝
val angleJoints = listOf(
Triple(BodyPart.LEFT_ELBOW, BodyPart.LEFT_SHOULDER, BodyPart.RIGHT_SHOULDER), // 左肩角度
Triple(BodyPart.RIGHT_ELBOW, BodyPart.RIGHT_SHOULDER, BodyPart.LEFT_SHOULDER), // 右肩角度
Triple(BodyPart.LEFT_SHOULDER, BodyPart.LEFT_ELBOW, BodyPart.LEFT_WRIST), // 左肘角度
Triple(BodyPart.RIGHT_SHOULDER, BodyPart.RIGHT_ELBOW, BodyPart.RIGHT_WRIST), // 右肘角度
Triple(BodyPart.LEFT_SHOULDER, BodyPart.LEFT_HIP, BodyPart.RIGHT_HIP), // 左髋角度
Triple(BodyPart.RIGHT_SHOULDER, BodyPart.RIGHT_HIP, BodyPart.LEFT_HIP), // 右髋角度
Triple(BodyPart.LEFT_HIP, BodyPart.LEFT_KNEE, BodyPart.LEFT_ANKLE), // 左膝角度
Triple(BodyPart.RIGHT_HIP, BodyPart.RIGHT_KNEE, BodyPart.RIGHT_ANKLE) // 右膝角度
)
for (joint in angleJoints) {
val p1 = keyPoints.find { it.bodyPart == joint.first }
val p2 = keyPoints.find { it.bodyPart == joint.second }
val p3 = keyPoints.find { it.bodyPart == joint.third }
// 确保三个关键点都检测到且置信度较高
if (p1 != null && p2 != null && p3 != null && p1.score > 0.2 && p2.score > 0.2 && p3.score > 0.2) { // 假设置信度阈值为0.2
angles.add(p2.abttPoints(p1, p2, p3).toDouble())
} else {
angles.add(0.0) // 如果关键点未检测到或置信度低角度设为0或NaN具体取决于你如何处理无效数据
}
}
return angles
} }
// 根据不同的 listType 判断标准 // 根据不同的 listType 判断标准
@ -90,32 +123,24 @@ class AngleDifference(private val angleObj: Angle) {
} }
// 计算角度差并根据角度值判断是否大于10返回相应的语句 // 计算角度差并根据角度值判断是否大于10返回相应的语句
fun calculateAngleDifference(listType: String): String { fun calculateAngleDifference(keyPoints: List<KeyPoint>, listType: String): String {
// 根据传入的参数选择不同的角度列表 // 根据传入的参数选择不同的角度列表 (标准角度)
val selectedList = when (listType) {
"1" -> angleObj.standList() // 选择站立姿势列表
"2" -> angleObj.deadliftList() // 选择硬拉列表
"3" -> angleObj.pushUpList() // 选择俯卧撑列表
"4" -> angleObj.squatList() // 选择深蹲列表
"5" -> angleObj.plankList() // 选择平板支撑列表
else -> throw IllegalArgumentException("未知的列表类型: $listType") // 处理无效的输入
}
// 获取另一个类中的角度列表
val angles = getAngles()
// 获取当前listType对应的标准列表
val standardList = getStandardForListType(listType) val standardList = getStandardForListType(listType)
// 获取从关键点计算出的角度列表
val angles = getAngles(keyPoints)
// 确保两个列表的大小一致 // 确保两个列表的大小一致
if (selectedList.size != angles.size || selectedList.size != standardList.size) { if (standardList.size != angles.size) {
throw IllegalArgumentException("三个列表的大小不一致!") // throw IllegalArgumentException("标准列表和计算出的角度列表大小不一致!")
// 如果大小不一致,可能是关键点没有全部检测到,返回一个提示信息而不是崩溃
return "关键点检测不完整,无法给出建议。"
} }
// 计算差值并检查条件 // 计算差值并检查条件
val differenceList = mutableListOf<Double>() val differenceList = mutableListOf<Double>()
for (i in selectedList.indices) { for (i in standardList.indices) {
differenceList.add((selectedList[i] - angles[i]).toDouble()) differenceList.add((standardList[i] - angles[i]))
} }
// 根据listType的不同提供不同标准的判断 // 根据listType的不同提供不同标准的判断
@ -131,21 +156,16 @@ class AngleDifference(private val angleObj: Angle) {
for (i in absoluteValues.indices) { for (i in absoluteValues.indices) {
val standard = standardList[i] val standard = standardList[i]
val calculatedAngle = angles[i]
val absoluteDifference = absoluteValues[i] val absoluteDifference = absoluteValues[i]
if (absoluteDifference > 10) {
// 仅对检测到且置信度较高的关键点进行角度判断
// 这里假设 getAngles 中已经处理了置信度低的,如果 getAngles 返回0.0表示无效,则跳过
if (calculatedAngle != 0.0 && absoluteDifference > 10) { // 角度差大于10度给出建议
// 根据角度差的正负判断提示语 // 根据角度差的正负判断提示语
val direction = if (differenceList[i] > 0) "增大" else "减小" val direction = if (differenceList[i] > 0) "增大" else "减小"
// 根据listType提供不同的标准和建议
warningMessages.add( warningMessages.add("${bodyParts[i]}角度(${String.format("%.1f", calculatedAngle)}°)与标准(${standard}°)相差过大,请注意调整!")
when (listType) {
"1" -> "${bodyParts[i]}的角度差值超出标准(${standard}°),请$direction 相应位置的角度!"
"2" -> "${bodyParts[i]}的角度差值超出标准(${standard}°),请$direction 相应位置的角度!"
"3" -> "${bodyParts[i]}的角度差值超出标准(${standard}°),请$direction 相应位置的角度!"
"4" -> "${bodyParts[i]}的角度差值超出标准(${standard}°),请$direction 相应位置的角度!"
"5" -> "${bodyParts[i]}的角度差值超出标准(${standard}°),请$direction 相应位置的角度!"
else -> "未知类型的角度差值超出标准,请检查!"
}
)
} }
} }

@ -310,7 +310,7 @@ class PoseNet(private val interpreter: Interpreter, private var gpuDelegate: Gpu
} }
// 提供外部访问 angles 的方法 // 提供外部访问 angles 的方法
fun getAngles(): List<Float> { val getAngles: List<Float> get(){
return angles.toList() // 返回 angles 的副本 return angles.toList() // 返回 angles 的副本
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 B

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#555555"/>
<size android:width="100dp" android:height="100dp"/>
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 774 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 753 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 B

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#1C1C1E"
tools:context=".ExerciseDetailActivity">
<!-- 顶部的图片区域 -->
<ImageView
android:id="@+id/exercise_detail_image"
android:layout_width="match_parent"
android:layout_height="250dp"
android:scaleType="centerCrop"
android:src="@drawable/placeholder_image" /> <!-- 这里的图片可以根据具体动作设置 -->
<!-- 返回按钮 -->
<ImageButton
android:id="@+id/back_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginStart="16dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="8dp"
android:src="@drawable/ic_back" /> <!-- 需要一个返回图标资源 -->
<!-- 动作名称 -->
<TextView
android:id="@+id/exercise_detail_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/exercise_detail_image"
android:layout_marginTop="16dp"
android:layout_marginStart="16dp"
android:textColor="#FFFFFF"
android:textSize="22sp"
android:textStyle="bold"
android:text="动作名称" />
<!-- 动作描述/注意事项 -->
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/exercise_detail_name"
android:layout_above="@id/start_training_button"
android:layout_marginTop="8dp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingBottom="8dp">
<TextView
android:id="@+id/exercise_detail_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#CCCCCC"
android:textSize="16sp"
android:text="这里是动作的详细描述和注意事项..." />
</ScrollView>
<!-- 开始训练按钮 -->
<Button
android:id="@+id/start_training_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:backgroundTint="#A020F0"
android:text="开始训练"
android:textColor="#FFFFFF"
android:textSize="18sp"/>
</RelativeLayout>

@ -10,6 +10,18 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />
<!-- 用于显示姿态建议的 TextView -->
<TextView
android:id="@+id/tv_pose_advice"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="100dp"
android:gravity="center_horizontal"
android:textColor="#FFFFFF"
android:textSize="18sp"
android:text="姿态建议将显示在这里"
android:background="#80000000" /> <!-- 半透明黑色背景 -->
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainTabActivity">
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="56dp" />
<LinearLayout
android:id="@+id/bottom_nav"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_gravity="bottom"
android:orientation="horizontal"
android:background="#222222">
<ImageView
android:id="@+id/nav_home"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:src="@drawable/home1"
android:scaleType="centerInside" />
<ImageView
android:id="@+id/nav_data"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:src="@drawable/data2"
android:scaleType="centerInside" />
<ImageView
android:id="@+id/nav_setting"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:src="@drawable/setting2"
android:scaleType="centerInside" />
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#1C1C1E">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="数据页面"
android:textColor="#FFF"
android:textSize="24sp"
android:layout_gravity="center"/>
</FrameLayout>

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#1C1C1E">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<!-- 硬拉卡片 -->
<include
android:id="@+id/deadlift_card"
layout="@layout/item_exercise_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp" />
<!-- 深蹲卡片 -->
<include
android:id="@+id/squat_card"
layout="@layout/item_exercise_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp" />
<!-- 平板支撑卡片 -->
<include
android:id="@+id/plank_card"
layout="@layout/item_exercise_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp" />
<!-- 引体向上卡片 -->
<include
android:id="@+id/pullup_card"
layout="@layout/item_exercise_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp" />
<!-- 俯卧撑卡片 -->
<include
android:id="@+id/pushup_card"
layout="@layout/item_exercise_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp" />
</LinearLayout>
</ScrollView>

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#1C1C1E">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="设置页面"
android:textColor="#FFF"
android:textSize="24sp"
android:layout_gravity="center"/>
</FrameLayout>

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:cardCornerRadius="8dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@+id/exercise_image"
android:layout_width="match_parent"
android:layout_height="200dp"
android:scaleType="centerCrop" />
<TextView
android:id="@+id/exercise_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:textColor="#000000"
android:textSize="18sp" />
<TextView
android:id="@+id/exercise_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:textColor="#666666"
android:textSize="14sp"
android:visibility="gone" />
</LinearLayout>
</androidx.cardview.widget.CardView>

@ -5,4 +5,4 @@
# For customization when using a Version Control System, please read the # For customization when using a Version Control System, please read the
# header note. # header note.
#Sat May 24 23:39:55 CST 2025 #Sat May 24 23:39:55 CST 2025
sdk.dir=C\:\\Users\\26891\\AppData\\Local\\Android\\Sdk sdk.dir=/Users/ziyue/Library/Android/sdk

Loading…
Cancel
Save