Ver código fonte

first commit

liuzhenxing 3 anos atrás
commit
bf78600fe7
47 arquivos alterados com 1913 adições e 0 exclusões
  1. 15 0
      .gitignore
  2. 3 0
      .idea/.gitignore
  3. 6 0
      .idea/compiler.xml
  4. 20 0
      .idea/gradle.xml
  5. 18 0
      .idea/misc.xml
  6. 1 0
      app/.gitignore
  7. 38 0
      app/build.gradle
  8. 21 0
      app/proguard-rules.pro
  9. 26 0
      app/src/androidTest/java/com/xplora/xpchat/ExampleInstrumentedTest.java
  10. 23 0
      app/src/main/AndroidManifest.xml
  11. 53 0
      app/src/main/java/com/xplora/xpchat/activity/AppsPager.java
  12. 53 0
      app/src/main/java/com/xplora/xpchat/activity/BaseActivity.java
  13. 13 0
      app/src/main/java/com/xplora/xpchat/activity/BasePager.java
  14. 157 0
      app/src/main/java/com/xplora/xpchat/activity/ChatActivity.java
  15. 11 0
      app/src/main/java/com/xplora/xpchat/adapter/FaceSelectAdapter.java
  16. 124 0
      app/src/main/java/com/xplora/xpchat/adapter/RecyclerAdapter.java
  17. 45 0
      app/src/main/java/com/xplora/xpchat/adapter/ViewPagerAdapter.java
  18. 63 0
      app/src/main/java/com/xplora/xpchat/model/AppModel.java
  19. 62 0
      app/src/main/java/com/xplora/xpchat/model/ContactModel.java
  20. 39 0
      app/src/main/java/com/xplora/xpchat/utils/Constant.java
  21. 69 0
      app/src/main/java/com/xplora/xpchat/utils/ResUtils.java
  22. 66 0
      app/src/main/java/com/xplora/xpchat/utils/ToolsUtils.java
  23. 28 0
      app/src/main/java/com/xplora/xpchat/view/AppItemView.java
  24. 27 0
      app/src/main/java/com/xplora/xpchat/view/FaceItemView.java
  25. 98 0
      app/src/main/java/com/xplora/xpchat/view/SideViewPager.java
  26. 170 0
      app/src/main/res/drawable-xhdpi/ic_launcher_background.xml
  27. BIN
      app/src/main/res/drawable-xhdpi/icon_app_add.png
  28. BIN
      app/src/main/res/drawable-xhdpi/icon_app_delete.png
  29. 170 0
      app/src/main/res/drawable/ic_launcher_background.xml
  30. 15 0
      app/src/main/res/layout/activity_main.xml
  31. 41 0
      app/src/main/res/layout/item_app_title.xml
  32. 33 0
      app/src/main/res/layout/item_contact.xml
  33. 17 0
      app/src/main/res/layout/view_screen_apps.xml
  34. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher.webp
  35. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
  36. 16 0
      app/src/main/res/values-night/themes.xml
  37. 10 0
      app/src/main/res/values/colors.xml
  38. 3 0
      app/src/main/res/values/strings.xml
  39. 16 0
      app/src/main/res/values/themes.xml
  40. 17 0
      app/src/test/java/com/xplora/xpchat/ExampleUnitTest.java
  41. 9 0
      build.gradle
  42. 21 0
      gradle.properties
  43. BIN
      gradle/wrapper/gradle-wrapper.jar
  44. 6 0
      gradle/wrapper/gradle-wrapper.properties
  45. 185 0
      gradlew
  46. 89 0
      gradlew.bat
  47. 16 0
      settings.gradle

+ 15 - 0
.gitignore

@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties

+ 3 - 0
.idea/.gitignore

@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml

+ 6 - 0
.idea/compiler.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CompilerConfiguration">
+    <bytecodeTargetLevel target="11" />
+  </component>
+</project>

+ 20 - 0
.idea/gradle.xml

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="GradleMigrationSettings" migrationVersion="1" />
+  <component name="GradleSettings">
+    <option name="linkedExternalProjectsSettings">
+      <GradleProjectSettings>
+        <option name="testRunner" value="GRADLE" />
+        <option name="distributionType" value="DEFAULT_WRAPPED" />
+        <option name="externalProjectPath" value="$PROJECT_DIR$" />
+        <option name="modules">
+          <set>
+            <option value="$PROJECT_DIR$" />
+            <option value="$PROJECT_DIR$/app" />
+          </set>
+        </option>
+        <option name="resolveModulePerSourceSet" value="false" />
+      </GradleProjectSettings>
+    </option>
+  </component>
+</project>

+ 18 - 0
.idea/misc.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="DesignSurface">
+    <option name="filePathToZoomLevelMap">
+      <map>
+        <entry key="app/src/main/res/layout/activity_main.xml" value="0.27355072463768115" />
+        <entry key="app/src/main/res/layout/item_app.xml" value="0.21354166666666666" />
+        <entry key="app/src/main/res/layout/item_app_title.xml" value="0.27355072463768115" />
+      </map>
+    </option>
+  </component>
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
+    <output url="file://$PROJECT_DIR$/build/classes" />
+  </component>
+  <component name="ProjectType">
+    <option name="id" value="Android" />
+  </component>
+</project>

+ 1 - 0
app/.gitignore

@@ -0,0 +1 @@
+/build

+ 38 - 0
app/build.gradle

@@ -0,0 +1,38 @@
+plugins {
+    id 'com.android.application'
+}
+
+android {
+    compileSdk 32
+
+    defaultConfig {
+        applicationId "com.xplora.xpchat"
+        minSdk 21
+        targetSdk 32
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+}
+
+dependencies {
+
+    implementation 'androidx.appcompat:appcompat:1.4.2'
+    implementation 'com.google.android.material:material:1.6.1'
+    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+    testImplementation 'junit:junit:4.13.2'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+}

+ 21 - 0
app/proguard-rules.pro

@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile

+ 26 - 0
app/src/androidTest/java/com/xplora/xpchat/ExampleInstrumentedTest.java

@@ -0,0 +1,26 @@
+package com.xplora.xpchat;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+    @Test
+    public void useAppContext() {
+        // Context of the app under test.
+        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        assertEquals("com.xplora.xpchat", appContext.getPackageName());
+    }
+}

+ 23 - 0
app/src/main/AndroidManifest.xml

@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.xplora.xpchat">
+
+    <application
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/Theme.XPChat">
+        <activity
+            android:name=".activity.ChatActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>

+ 53 - 0
app/src/main/java/com/xplora/xpchat/activity/AppsPager.java

@@ -0,0 +1,53 @@
+package com.xplora.xpchat.activity;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.pm.ResolveInfo;
+import android.view.LayoutInflater;
+
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.xplora.xpchat.R;
+import com.xplora.xpchat.adapter.RecyclerAdapter;
+import com.xplora.xpchat.model.AppModel;
+import com.xplora.xpchat.utils.Constant;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class AppsPager extends BasePager {
+    private RecyclerAdapter mRecyclerAdapter;
+
+    public AppsPager(Context context, List<AppModel> appsList, Constant.E_PAGER_TYPE pagerType, int viewIndex, int appTitleStatus, int deleteBtnStatus, RecyclerAdapter.AppItemOnClickListener listener) {
+        super(context);
+
+        mBaseView = LayoutInflater.from(mContext).inflate(R.layout.view_screen_apps, null);
+        RecyclerView mRecyclerView = (RecyclerView)mBaseView.findViewById(R.id.recyclerView);
+
+        RecyclerView.LayoutManager mLayoutManager = new GridLayoutManager(mContext, 2);
+        mRecyclerView.setHasFixedSize(true);
+        mRecyclerView.setLayoutManager(mLayoutManager);
+
+        mRecyclerAdapter = new RecyclerAdapter(mContext, appsList, pagerType, viewIndex, appTitleStatus, deleteBtnStatus);
+        mRecyclerAdapter.setListOnClickListener(listener);
+        mRecyclerView.setAdapter(mRecyclerAdapter);
+    }
+
+    public void refreshApp(List<AppModel> appsList) {
+        mRecyclerAdapter.setAppList(appsList);
+        mRecyclerAdapter.notifyDataSetChanged();
+    }
+
+    @SuppressLint("NotifyDataSetChanged")
+    public void refreshAppTitleStatus(int appTitleStatus) {
+        mRecyclerAdapter.setAppTitleStatus(appTitleStatus);
+        mRecyclerAdapter.notifyDataSetChanged();
+    }
+
+    @SuppressLint("NotifyDataSetChanged")
+    public void refreshDeleteBtnStatus(int deleteBtnStatus) {
+        mRecyclerAdapter.setDeleteBtnStatus(deleteBtnStatus);
+        mRecyclerAdapter.notifyDataSetChanged();
+    }
+}

+ 53 - 0
app/src/main/java/com/xplora/xpchat/activity/BaseActivity.java

@@ -0,0 +1,53 @@
+package com.xplora.xpchat.activity;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Vibrator;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+
+import androidx.annotation.Nullable;
+
+public class BaseActivity extends Activity {
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        //全屏
+        setFullView();
+        setHardKey();
+
+        onCreateBase();
+        initDataBase();
+        initViewBase();
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        onDestroyBase();
+    }
+
+    protected void onCreateBase() { }
+    protected void initDataBase() { }
+    protected void initViewBase() { }
+    protected void onDestroyBase() { }
+
+    private void setFullView() {
+        this.requestWindowFeature(Window.FEATURE_NO_TITLE);
+        this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
+    }
+
+    private void setHardKey() {
+        View decorView = getWindow().getDecorView();
+        int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+                | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_FULLSCREEN;
+        decorView.setSystemUiVisibility(uiOptions);
+    }
+
+    public void actionWithVibrator() {
+        Vibrator vibrator = (Vibrator)getSystemService(VIBRATOR_SERVICE);
+        vibrator.vibrate(100);
+    }
+}

+ 13 - 0
app/src/main/java/com/xplora/xpchat/activity/BasePager.java

@@ -0,0 +1,13 @@
+package com.xplora.xpchat.activity;
+
+import android.content.Context;
+import android.view.View;
+
+public class BasePager {
+    protected Context mContext;
+    public View mBaseView;
+
+    public BasePager(Context context) {
+        mContext = context;
+    }
+}

+ 157 - 0
app/src/main/java/com/xplora/xpchat/activity/ChatActivity.java

@@ -0,0 +1,157 @@
+package com.xplora.xpchat.activity;
+
+import androidx.annotation.NonNull;
+import androidx.viewpager.widget.ViewPager;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.view.View;
+import android.widget.Button;
+
+import com.xplora.xpchat.R;
+import com.xplora.xpchat.adapter.RecyclerAdapter;
+import com.xplora.xpchat.adapter.ViewPagerAdapter;
+//import com.xplora.xpchat.data.DataManager;
+import com.xplora.xpchat.model.AppModel;
+import com.xplora.xpchat.model.ContactModel;
+import com.xplora.xpchat.utils.Constant;
+import com.xplora.xpchat.utils.ResUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ChatActivity extends BaseActivity {
+    private int mCurrentPagerIndex = Constant.HOME_INDEX;
+    private int mAppTitleStatus = 1;
+    private int mDeleteBtnStatus = 0;
+    private int mWarningStatus = 1; //表盘界面,是否显示 warning 及状态, 0:不显示; >0 显示
+
+    private final List<View> mPagesDatas = new ArrayList<>();
+    private final List<Object> mPagerViews = new ArrayList<>();
+    private ViewPagerAdapter mViewPagerAdapter = null;
+
+    private Button mWarningButton;
+
+    @Override
+    protected void onCreateBase() {
+        setContentView(R.layout.activity_main);
+    }
+
+    @Override
+    protected void initDataBase() {
+        super.initDataBase();
+
+        //监听数据库
+        //DataManager.registerContentObserver(new RecentContentObserver(mHandler), new ContactContentObserver(mHandler), new SettingContentObserver(mHandler));
+    }
+
+    @Override
+    protected void initViewBase() {
+        super.initViewBase();
+        initViewPager();
+    }
+
+    private void initViewPager() {
+        //apps
+        initInstalledAppsPager();
+
+        ViewPager viewPager = (ViewPager) findViewById(R.id.viewPager);
+        mViewPagerAdapter = new ViewPagerAdapter(mPagesDatas);//创建适配器对象
+        viewPager.setAdapter(mViewPagerAdapter);//设置适配器
+        viewPager.setCurrentItem(mCurrentPagerIndex);
+        viewPager.addOnPageChangeListener(new ViewPageChangeListener());
+    }
+
+    private void initInstalledAppsPager() {
+        List<AppModel> installedApps = new ArrayList<>();
+        BasePager appsPager = new AppsPager(this, installedApps, Constant.E_PAGER_TYPE.APPLIST, 0, mAppTitleStatus, mDeleteBtnStatus, onAppItemOnClickListener);
+        appsPager.mBaseView.setOnClickListener(new AppsPagerOnClickListener());
+        appsPager.mBaseView.setOnLongClickListener(new AppsPagerOnClickListener());
+
+        mPagesDatas.add(appsPager.mBaseView);
+        mPagerViews.add(appsPager);
+    }
+
+    // ======================== Listener =================================
+    public class ViewPageChangeListener implements ViewPager.OnPageChangeListener {
+        @Override
+        public void onPageSelected(int position) {
+            mCurrentPagerIndex = position;
+        }
+
+        @Override
+        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+        }
+        @Override
+        public void onPageScrollStateChanged(int state) {
+        }
+    }
+
+    RecyclerAdapter.AppItemOnClickListener onAppItemOnClickListener = new RecyclerAdapter.AppItemOnClickListener() {
+        @Override
+        public void onClick(Constant.E_PAGER_TYPE pagerType, int viewIndex, int appIndex, AppModel appModel) {
+
+        }
+
+        @Override
+        public void onLongClick(Constant.E_PAGER_TYPE pagerType, int viewIndex, int appIndex, AppModel appModel) {
+            if (pagerType == Constant.E_PAGER_TYPE.COMMON) {
+            }
+        }
+
+        @Override
+        public void onClickDelete(Constant.E_PAGER_TYPE pagerType, int viewIndex, int appIndex, AppModel appModel) {
+
+        }
+    };
+
+    public class AppsPagerOnClickListener implements View.OnClickListener, View.OnLongClickListener {
+        @Override
+        public void onClick(View v) {
+
+        }
+
+        @Override
+        public boolean onLongClick(View v) {
+            return true;
+        }
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        if (data == null)
+            return;
+        String action = data.getAction();
+    }
+
+    // ======================== 监听数据库 =======================
+    public class ContactContentObserver extends ContentObserver {
+        public ContactContentObserver(Handler handler) {
+            super(handler);
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            super.onChange(selfChange);
+        }
+    }
+
+    Handler mHandler = new Handler(Looper.myLooper()) {
+        @Override
+        public void handleMessage(@NonNull Message msg) {
+            super.handleMessage(msg);
+        }
+    };
+
+
+    @Override
+    protected void onDestroyBase() {
+        super.onDestroyBase();
+    }
+}

+ 11 - 0
app/src/main/java/com/xplora/xpchat/adapter/FaceSelectAdapter.java

@@ -0,0 +1,11 @@
+package com.xplora.xpchat.adapter;
+
+import android.view.View;
+
+import java.util.List;
+
+public class FaceSelectAdapter extends ViewPagerAdapter{
+    public FaceSelectAdapter(List<View> list) {
+        super(list);
+    }
+}

+ 124 - 0
app/src/main/java/com/xplora/xpchat/adapter/RecyclerAdapter.java

@@ -0,0 +1,124 @@
+package com.xplora.xpchat.adapter;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.pm.ResolveInfo;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.xplora.xpchat.R;
+import com.xplora.xpchat.model.AppModel;
+import com.xplora.xpchat.utils.Constant;
+import com.xplora.xpchat.view.AppItemView;
+
+import java.util.List;
+
+public class RecyclerAdapter extends RecyclerView.Adapter<AppItemView> {
+    private Context mContext;
+    private List<AppModel> mAppsList;
+    private Constant.E_PAGER_TYPE mPagerType;
+    private int mViewIndex;
+    private int mAppTitleStatus;
+    private int mDeleteBtnStatus;
+    private AppItemOnClickListener mItemOnClickListener;
+
+    public RecyclerAdapter(Context context, List<AppModel> appList, Constant.E_PAGER_TYPE pagerType, int viewIndex, int appTitleStatus, int deleteBtnStatus) {
+        mContext = context;
+        mAppsList = appList;
+        mPagerType = pagerType;
+        mViewIndex = viewIndex;
+        mAppTitleStatus = appTitleStatus;
+        mDeleteBtnStatus = deleteBtnStatus;
+    }
+
+    @NonNull
+    @Override
+    public AppItemView onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
+        int layoutId = R.layout.item_app_title;
+        View view = LayoutInflater.from(viewGroup.getContext()).inflate(layoutId, viewGroup, false);
+        AppItemView holder = new AppItemView(view);
+        return holder;
+    }
+
+    @Override
+    public void onBindViewHolder(@NonNull AppItemView viewHolder, @SuppressLint("RecyclerView") int i) {
+        AppModel appModel = mAppsList.get(i);
+        if (mPagerType == Constant.E_PAGER_TYPE.COMMON && appModel.isEmptyStatus()) {
+            setAppEmptyViewHolder(viewHolder, i, appModel);
+        } else {
+            setAppViewHolder(viewHolder, i, appModel);
+        }
+    }
+
+    public void setAppEmptyViewHolder(@NonNull AppItemView viewHolder, int index, AppModel appModel) {
+        viewHolder.mAppIcon.setImageResource(R.drawable.icon_app_add);
+        viewHolder.mAppName.setText("");
+        viewHolder.mDeleteButton.setVisibility(View.GONE);
+        viewHolder.mAppIcon.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mItemOnClickListener.onClick(mPagerType, mViewIndex, index, mAppsList.get(index));
+            }
+        });
+    }
+
+    public void setAppViewHolder(@NonNull AppItemView viewHolder, int index, AppModel appModel) {
+        viewHolder.mAppIcon.setImageDrawable(appModel.getAppIcon());
+        viewHolder.mAppName.setText(appModel.getAppLabel());
+        int status = mPagerType != Constant.E_PAGER_TYPE.COMMON ? View.GONE : (mDeleteBtnStatus == 0 ? View.GONE : View.VISIBLE);
+        viewHolder.mDeleteButton.setVisibility(status);
+
+        viewHolder.mDeleteButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mItemOnClickListener.onClickDelete(mPagerType, mViewIndex, index, mAppsList.get(index));
+            }
+        });
+        viewHolder.mAppIcon.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mItemOnClickListener.onClick(mPagerType, mViewIndex, index, mAppsList.get(index));
+            }
+        });
+        viewHolder.mAppIcon.setOnLongClickListener(new View.OnLongClickListener() {
+            @Override
+            public boolean onLongClick(View v) {
+                mItemOnClickListener.onLongClick(mPagerType, mViewIndex, index, mAppsList.get(index));
+                return true;
+            }
+        });
+    }
+
+    @Override
+    public int getItemCount() {
+        return mAppsList.size();
+    }
+
+    public void setAppList(List<AppModel> appList) {
+        mAppsList = appList;
+    }
+
+    public void setAppTitleStatus(int appTitleStatus) {
+        mAppTitleStatus = appTitleStatus;
+    }
+
+    public void setDeleteBtnStatus(int deleteBtnStatus) {
+        mDeleteBtnStatus = deleteBtnStatus;
+    }
+
+    public void setListOnClickListener(AppItemOnClickListener listener) {
+        this.mItemOnClickListener = listener;
+    }
+
+    public interface AppItemOnClickListener {
+        void onClick(Constant.E_PAGER_TYPE pagerType, int viewIndex, int appIndex, AppModel appModel);
+        void onLongClick(Constant.E_PAGER_TYPE pagerType, int viewIndex, int appIndex, AppModel appModel);
+        void onClickDelete(Constant.E_PAGER_TYPE pagerType, int viewIndex, int appIndex, AppModel appModel);
+    }
+}

+ 45 - 0
app/src/main/java/com/xplora/xpchat/adapter/ViewPagerAdapter.java

@@ -0,0 +1,45 @@
+package com.xplora.xpchat.adapter;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.viewpager.widget.PagerAdapter;
+
+import java.util.List;
+
+public class ViewPagerAdapter extends PagerAdapter {
+    private List<View> pagelist;
+
+    public ViewPagerAdapter(List<View> list) {
+        this.pagelist=list;
+    }
+
+    @Override
+    public int getCount() {
+        return pagelist.size();
+    }
+
+    @NonNull
+    @Override
+    public Object instantiateItem(@NonNull ViewGroup container, int position) {
+        container.addView(pagelist.get(position));
+        return pagelist.get(position);
+    }
+
+    @Override
+    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
+        container.removeView(pagelist.get(position));
+    }
+
+    @Override
+    public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
+        return view==object;
+    }
+
+    //解决数据不刷新的问题
+    @Override
+    public int getItemPosition(Object object) {
+        return POSITION_NONE;
+    }
+}

+ 63 - 0
app/src/main/java/com/xplora/xpchat/model/AppModel.java

@@ -0,0 +1,63 @@
+package com.xplora.xpchat.model;
+
+import android.graphics.drawable.Drawable;
+
+public class AppModel {
+    private String packageName = ""; //应用程序所对应的包名
+    private String activityName = ""; //activity名
+    private String appLabel = ""; //应用程序标签
+    private Drawable appIcon = null; //应用程序图像
+
+
+    public String getAppLabel() {
+        return appLabel;
+    }
+
+    public void setAppLabel(String appLabel) {
+        this.appLabel = appLabel;
+    }
+
+    public Drawable getAppIcon() {
+        return appIcon;
+    }
+
+    public void setAppIcon(Drawable appIcon) {
+        this.appIcon = appIcon;
+    }
+
+    public String getPackageName() {
+        return packageName;
+    }
+
+    public void setPackageName(String pkgName) {
+        packageName = pkgName;
+    }
+
+    public String getActivityName() {
+        return activityName;
+    }
+
+    public void setActivityName(String activityName) {
+        this.activityName = activityName;
+    }
+
+    public boolean isEmptyStatus() {
+        return packageName.equals("");
+    }
+
+    public void setEmptyStatus() {
+        this.packageName = "";
+        this.activityName = "";
+        this.appLabel = "";
+        this.appIcon = null;
+    }
+
+    public AppModel copy() {
+        AppModel appModel = new AppModel();
+        appModel.setPackageName(this.getPackageName());
+        appModel.setActivityName(this.getActivityName());
+        appModel.setAppLabel(this.getAppLabel());
+        appModel.setAppIcon(this.getAppIcon());
+        return appModel;
+    }
+}

+ 62 - 0
app/src/main/java/com/xplora/xpchat/model/ContactModel.java

@@ -0,0 +1,62 @@
+package com.xplora.xpchat.model;
+
+import android.graphics.drawable.Drawable;
+
+import java.io.Serializable;
+
+public class ContactModel implements Serializable {
+    private String userId = "";
+    private String photo = "";
+    private String name = "";
+    private String tel = "";
+    private int callType = 0;
+    private int callTime = 0;
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+
+    public String getPhoto() {
+        return photo;
+    }
+
+    public void setPhoto(String photo) {
+        this.photo = photo;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getTel() {
+        return tel;
+    }
+
+    public void setTel(String tel) {
+        this.tel = tel;
+    }
+
+    public int getCallTime() {
+        return callTime;
+    }
+
+    public void setCallTime(int callTime) {
+        this.callTime = callTime;
+    }
+
+    public int getCallType() {
+        return callType;
+    }
+
+    public void setCallType(int callType) {
+        this.callType = callType;
+    }
+}

+ 39 - 0
app/src/main/java/com/xplora/xpchat/utils/Constant.java

@@ -0,0 +1,39 @@
+package com.xplora.xpchat.utils;
+
+public class Constant {
+
+    public static boolean DEBUG = true;
+    public static boolean DEBUG_ALL_APPS = true; //安装的应用不筛选app
+
+    public static int COUNT_VIEW_APP = 4; //每个界面 app数量
+    public static int COUNT_QUICK_APP = COUNT_VIEW_APP * 2; //快捷应用的个数
+
+    public static long TIME_ANIM_CLOCK = 60*1000; //毫秒
+
+    public static int HOME_INDEX = 1;
+
+    public static int W_FACE_CLOCK_A = 2; //第2套表盘
+    public static int W_FACE_CLOCK_B = 3; //第3套表盘
+
+    public static String PACKAGE_NAME = "com.xplora.xpchat";
+    public static String PACKAGE_NAME_FILTER = "com.xplora.";
+    public static String PACKAGE_NAME_FILTER_LAUNCHER = "com.xplora.xpchat";
+    public static String FORMAT_DATE = "MM.dd.EEE";
+
+    public static String FACE_SELECT = "face_select_";
+    public static String FACE_BG = "face_bg_";
+
+    public static String INTENT_SELECT_FACE = "intent_select_face";
+    public static String INTENT_SELECT_PACKAGENAME = "intent_select_packagename";
+    public static String INTENT_PAGER_INDEX = "intent_pager_index";
+    public static String INTENT_ITEM_INDEX = "intent_item_index";
+
+    public enum E_PAGER_TYPE {
+        RECENT,
+        CONTACTS,
+        ZERO,
+        HOME,
+        COMMON,
+        APPLIST
+    };
+}

+ 69 - 0
app/src/main/java/com/xplora/xpchat/utils/ResUtils.java

@@ -0,0 +1,69 @@
+package com.xplora.xpchat.utils;
+import android.content.Context;
+
+public class ResUtils {
+    private static Context mSaveContext = null;
+    private static String mSavePackageName = null;
+
+    public static void initResUtils(Context context, String name) {
+        mSaveContext = context;
+        mSavePackageName = name;
+    }
+
+    public static String getString(int strId,Object...formatArgs) {
+        if (mSaveContext == null)
+            return null;
+        return mSaveContext.getResources().getString(strId, formatArgs);
+    }
+
+    public static int getStringId(String prefix, int index) {
+        StringBuilder builder = new StringBuilder();
+        builder.append(prefix).append(index);
+
+        return ResUtils.getResourcesId(builder.toString(), "string");
+    }
+
+    public static int getImageId(String prefix, int index) {
+        StringBuilder builder = new StringBuilder();
+        builder.append(prefix).append(index);
+
+        return ResUtils.getResourcesId(builder.toString(), "drawable");
+    }
+
+    public static int getWidgetId(String prefix, int index) {
+        StringBuilder builder = new StringBuilder();
+        builder.append(prefix).append(index);
+
+        return ResUtils.getResourcesId(builder.toString(), "id");
+    }
+
+    public static int getLayoutId(String prefix, int index) {
+        StringBuilder builder = new StringBuilder();
+        builder.append(prefix).append(index);
+
+        return ResUtils.getResourcesId(builder.toString(), "layout");
+    }
+
+    public static int getResourcesId(String imageName, String defType) {
+        if (mSaveContext == null)
+            return 0;
+
+        return mSaveContext.getResources().getIdentifier(imageName, defType, mSavePackageName);
+    }
+
+    /**
+     * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
+     */
+    public static int dip2px(float dpValue) {
+        final float scale = mSaveContext.getResources().getDisplayMetrics().density;
+        return (int) (dpValue * scale + 0.5f);
+    }
+
+    /**
+     * 根据手机的分辨率从 px(像素) 的单位 转成为 dp
+     */
+    public static int px2dip(float pxValue) {
+        final float scale = mSaveContext.getResources().getDisplayMetrics().density;
+        return (int) (pxValue / scale + 0.5f);
+    }
+}

+ 66 - 0
app/src/main/java/com/xplora/xpchat/utils/ToolsUtils.java

@@ -0,0 +1,66 @@
+package com.xplora.xpchat.utils;
+
+import java.sql.Date;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+import java.util.Locale;
+
+public class ToolsUtils {
+    public static boolean isNullOrEmpty(String string) {
+        if (string == null || string.isEmpty())
+            return true;
+        else
+            return false;
+    }
+
+    public static int getCurrentTimeSecond() {
+        long time = System.currentTimeMillis() / 1000;
+        int curTime = (int) time;
+        return curTime;
+    }
+
+    public static long getCurrentTimeMillis() {
+        return System.currentTimeMillis();
+    }
+
+    public static int getSecond() {
+        long time = getCurrentTimeMillis();
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTimeInMillis(time);
+        return calendar.get(Calendar.SECOND);
+    }
+
+    public static int[] getHourMinute() {
+        long time = getCurrentTimeMillis();
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTimeInMillis(time);
+        int hour = calendar.get(Calendar.HOUR_OF_DAY);
+        int miniute = calendar.get(Calendar.MINUTE);
+        return new int[]{hour, miniute};
+    }
+
+    public static Date getDate() {
+        long time = getCurrentTimeMillis();
+        return new Date(time);
+    }
+
+    public static Date getDate(long time) {
+        return new Date(time);
+    }
+
+    public static String getNumberString(int value) {
+        return value >= 10 ?  ("" + value) : ("0" + value);
+    }
+
+    public static String getFormatDate(String format) {
+        SimpleDateFormat sdf = new SimpleDateFormat(format, Locale.getDefault());
+        return sdf.format(getDate());
+    }
+
+    public static float getSecondDegrees() {
+        int second = getSecond();
+        return second * (360/60);
+    }
+}

+ 28 - 0
app/src/main/java/com/xplora/xpchat/view/AppItemView.java

@@ -0,0 +1,28 @@
+package com.xplora.xpchat.view;
+
+import android.content.Context;
+import android.content.pm.ResolveInfo;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.xplora.xpchat.R;
+
+import java.util.List;
+
+
+public class AppItemView extends RecyclerView.ViewHolder{
+    public ImageView mAppIcon;
+    public TextView mAppName;
+    public Button mDeleteButton;
+    public AppItemView(@NonNull View itemView) {
+        super(itemView);
+        mAppIcon = itemView.findViewById(R.id.appIcon);
+        mAppName = itemView.findViewById(R.id.appName);
+        mDeleteButton = itemView.findViewById(R.id.btn_delete);
+    }
+}

+ 27 - 0
app/src/main/java/com/xplora/xpchat/view/FaceItemView.java

@@ -0,0 +1,27 @@
+package com.xplora.xpchat.view;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+
+public class FaceItemView extends View {
+
+    public FaceItemView(Context context) {
+        super(context);
+    }
+
+    public FaceItemView(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public FaceItemView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public FaceItemView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+}

+ 98 - 0
app/src/main/java/com/xplora/xpchat/view/SideViewPager.java

@@ -0,0 +1,98 @@
+package com.xplora.xpchat.view;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.viewpager.widget.ViewPager;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+
+public class SideViewPager extends ViewPager {
+    /**
+     * 开始点击的位置
+     */
+    private int startX;
+    /**
+     * 临界值
+     */
+    private int criticalValue = 200;
+
+    public SideViewPager(@NonNull Context context) {
+        super(context);
+    }
+
+    public SideViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    /**
+     * 边界滑动回调
+     */
+    public interface onSideListener {
+        /**
+         * 左边界回调
+         */
+        void onLeftSide();
+
+        /**
+         * 右边界回调
+         */
+        void onRightSide();
+    }
+
+    /**
+     * 回调
+     */
+    private onSideListener mOnSideListener;
+
+    /**
+     * 设置回调
+     *
+     * @param listener
+     */
+    public void setOnSideListener(onSideListener listener) {
+        this.mOnSideListener = listener;
+    }
+
+    /**
+     * 设置临界值
+     *
+     * @param criticalValue
+     */
+    public void setCriticalValue(int criticalValue) {
+        this.criticalValue = criticalValue;
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent event) {
+        switch (event.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                startX = (int) event.getX();
+                break;
+        }
+        return super.dispatchTouchEvent(event);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        switch (event.getAction()) {
+            case MotionEvent.ACTION_MOVE:
+                if (startX - event.getX() > criticalValue && (getCurrentItem() == getAdapter().getCount() - 1)) {
+                    if (null != mOnSideListener) {
+                        mOnSideListener.onRightSide();
+                    }
+                }
+                if ((event.getX() - startX) > criticalValue && (getCurrentItem() == 0)) {
+                    if (null != mOnSideListener) {
+                        mOnSideListener.onLeftSide();
+                    }
+                }
+                break;
+            default:
+                break;
+        }
+        return super.onTouchEvent(event);
+    }
+}

+ 170 - 0
app/src/main/res/drawable-xhdpi/ic_launcher_background.xml

@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path
+        android:fillColor="#3DDC84"
+        android:pathData="M0,0h108v108h-108z" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M9,0L9,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,0L19,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,0L29,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,0L39,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,0L49,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,0L59,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,0L69,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,0L79,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M89,0L89,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M99,0L99,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,9L108,9"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,19L108,19"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,29L108,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,39L108,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,49L108,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,59L108,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,69L108,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,79L108,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,89L108,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,99L108,99"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,29L89,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,39L89,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,49L89,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,59L89,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,69L89,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,79L89,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,19L29,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,19L39,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,19L49,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,19L59,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,19L69,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,19L79,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+</vector>

BIN
app/src/main/res/drawable-xhdpi/icon_app_add.png


BIN
app/src/main/res/drawable-xhdpi/icon_app_delete.png


+ 170 - 0
app/src/main/res/drawable/ic_launcher_background.xml

@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path
+        android:fillColor="#3DDC84"
+        android:pathData="M0,0h108v108h-108z" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M9,0L9,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,0L19,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,0L29,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,0L39,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,0L49,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,0L59,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,0L69,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,0L79,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M89,0L89,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M99,0L99,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,9L108,9"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,19L108,19"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,29L108,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,39L108,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,49L108,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,59L108,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,69L108,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,79L108,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,89L108,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,99L108,99"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,29L89,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,39L89,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,49L89,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,59L89,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,69L89,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,79L89,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,19L29,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,19L39,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,19L49,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,19L59,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,19L69,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,19L79,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+</vector>

+ 15 - 0
app/src/main/res/layout/activity_main.xml

@@ -0,0 +1,15 @@
+<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"
+    tools:context=".activity.ChatAcivity">
+
+    <androidx.viewpager.widget.ViewPager
+        android:id="@+id/viewPager"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        tools:ignore="MissingConstraints"
+        />
+
+</RelativeLayout>

+ 41 - 0
app/src/main/res/layout/item_app_title.xml

@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="70dp"
+    android:layout_height="70dp">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical"
+        android:gravity="center"
+        >
+
+        <ImageView
+            android:layout_width="55dp"
+            android:layout_height="55dp"
+            android:src = "@mipmap/ic_launcher"
+            android:scaleType="fitXY"
+            android:id="@+id/appIcon"
+            />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="app name"
+            android:textColor="@color/white"
+            android:textSize="8dp"
+            android:gravity="center"
+            android:id="@+id/appName"/>
+    </LinearLayout>
+
+    <Button
+        android:id="@+id/btn_delete"
+        android:layout_width="17dp"
+        android:layout_height="17dp"
+        android:layout_marginTop="7dp"
+        android:layout_marginEnd="7dp"
+        android:layout_alignParentEnd="true"
+        android:background="@drawable/icon_app_delete"
+        />
+
+</RelativeLayout>

+ 33 - 0
app/src/main/res/layout/item_contact.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="90dp"
+    android:layout_height="70dp"
+    >
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical"
+        android:gravity="center"
+        >
+
+        <ImageView
+            android:id="@+id/item_head"
+            android:layout_width="55dp"
+            android:layout_height="55dp"
+            android:src = "@mipmap/ic_launcher"
+            android:scaleType="fitXY"
+            />
+
+        <TextView
+            android:id="@+id/item_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="-1dp"
+            android:textFontWeight="500"
+            android:textSize="10sp"
+            android:textColor="@color/white"
+            />
+    </LinearLayout>
+
+</RelativeLayout>

+ 17 - 0
app/src/main/res/layout/view_screen_apps.xml

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="20dp"
+        android:layout_marginEnd="20dp"
+        android:layout_marginTop="30dp"
+        android:layout_marginBottom="30dp"
+        android:id="@+id/recyclerView"
+        android:scrollbars="vertical"
+        />
+
+</RelativeLayout>

BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.webp


BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp


+ 16 - 0
app/src/main/res/values-night/themes.xml

@@ -0,0 +1,16 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <!-- Base application theme. -->
+    <style name="Theme.XPChat" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+        <!-- Primary brand color. -->
+        <item name="colorPrimary">@color/purple_200</item>
+        <item name="colorPrimaryVariant">@color/purple_700</item>
+        <item name="colorOnPrimary">@color/black</item>
+        <!-- Secondary brand color. -->
+        <item name="colorSecondary">@color/teal_200</item>
+        <item name="colorSecondaryVariant">@color/teal_200</item>
+        <item name="colorOnSecondary">@color/black</item>
+        <!-- Status bar color. -->
+        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
+        <!-- Customize your theme here. -->
+    </style>
+</resources>

+ 10 - 0
app/src/main/res/values/colors.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="purple_200">#FFBB86FC</color>
+    <color name="purple_500">#FF6200EE</color>
+    <color name="purple_700">#FF3700B3</color>
+    <color name="teal_200">#FF03DAC5</color>
+    <color name="teal_700">#FF018786</color>
+    <color name="black">#FF000000</color>
+    <color name="white">#FFFFFFFF</color>
+</resources>

+ 3 - 0
app/src/main/res/values/strings.xml

@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">XPChat</string>
+</resources>

+ 16 - 0
app/src/main/res/values/themes.xml

@@ -0,0 +1,16 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <!-- Base application theme. -->
+    <style name="Theme.XPChat" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+        <!-- Primary brand color. -->
+        <item name="colorPrimary">@color/purple_500</item>
+        <item name="colorPrimaryVariant">@color/purple_700</item>
+        <item name="colorOnPrimary">@color/white</item>
+        <!-- Secondary brand color. -->
+        <item name="colorSecondary">@color/teal_200</item>
+        <item name="colorSecondaryVariant">@color/teal_700</item>
+        <item name="colorOnSecondary">@color/black</item>
+        <!-- Status bar color. -->
+        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
+        <!-- Customize your theme here. -->
+    </style>
+</resources>

+ 17 - 0
app/src/test/java/com/xplora/xpchat/ExampleUnitTest.java

@@ -0,0 +1,17 @@
+package com.xplora.xpchat;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+public class ExampleUnitTest {
+    @Test
+    public void addition_isCorrect() {
+        assertEquals(4, 2 + 2);
+    }
+}

+ 9 - 0
build.gradle

@@ -0,0 +1,9 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+    id 'com.android.application' version '7.1.2' apply false
+    id 'com.android.library' version '7.1.2' apply false
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}

+ 21 - 0
gradle.properties

@@ -0,0 +1,21 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app"s APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true

BIN
gradle/wrapper/gradle-wrapper.jar


+ 6 - 0
gradle/wrapper/gradle-wrapper.properties

@@ -0,0 +1,6 @@
+#Thu Jul 07 17:10:02 CST 2022
+distributionBase=GRADLE_USER_HOME
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
+distributionPath=wrapper/dists
+zipStorePath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME

+ 185 - 0
gradlew

@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=`expr $i + 1`
+    done
+    case $i in
+        0) set -- ;;
+        1) set -- "$args0" ;;
+        2) set -- "$args0" "$args1" ;;
+        3) set -- "$args0" "$args1" "$args2" ;;
+        4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"

+ 89 - 0
gradlew.bat

@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem      https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega

+ 16 - 0
settings.gradle

@@ -0,0 +1,16 @@
+pluginManagement {
+    repositories {
+        gradlePluginPortal()
+        google()
+        mavenCentral()
+    }
+}
+dependencyResolutionManagement {
+    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+    repositories {
+        google()
+        mavenCentral()
+    }
+}
+rootProject.name = "XPChat"
+include ':app'