詹子聪 5 lat temu
commit
aaa9d696aa
71 zmienionych plików z 3699 dodań i 0 usunięć
  1. 14 0
      .gitignore
  2. 1 0
      app/.gitignore
  3. 34 0
      app/build.gradle
  4. 21 0
      app/proguard-rules.pro
  5. 26 0
      app/src/androidTest/java/com/itant/metrowc/ExampleInstrumentedTest.java
  6. 30 0
      app/src/main/AndroidManifest.xml
  7. BIN
      app/src/main/assets/db/metro.db
  8. BIN
      app/src/main/assets/map/img/wc.png
  9. 69 0
      app/src/main/assets/map/metro.html
  10. 135 0
      app/src/main/java/com/itant/metrowc/MainActivity.java
  11. 23 0
      app/src/main/java/com/itant/metrowc/MetroApplication.java
  12. 31 0
      app/src/main/java/com/itant/metrowc/adapter/FragmentPagerItemAdapter.java
  13. 32 0
      app/src/main/java/com/itant/metrowc/base/BaseFragment.java
  14. 17 0
      app/src/main/java/com/itant/metrowc/bean/City.java
  15. 12 0
      app/src/main/java/com/itant/metrowc/bean/Country.java
  16. 20 0
      app/src/main/java/com/itant/metrowc/bean/Exit.java
  17. 90 0
      app/src/main/java/com/itant/metrowc/bean/Line.java
  18. 269 0
      app/src/main/java/com/itant/metrowc/bean/Station.java
  19. 85 0
      app/src/main/java/com/itant/metrowc/manager/ActivityManager.java
  20. 22 0
      app/src/main/java/com/itant/metrowc/manager/DatabaseManager.java
  21. 133 0
      app/src/main/java/com/itant/metrowc/manager/DbManager.java
  22. 80 0
      app/src/main/java/com/itant/metrowc/manager/DialogManager.java
  23. 25 0
      app/src/main/java/com/itant/metrowc/manager/StationManager.java
  24. 26 0
      app/src/main/java/com/itant/metrowc/manager/ThreadManager.java
  25. 56 0
      app/src/main/java/com/itant/metrowc/manager/ToastManager.java
  26. 48 0
      app/src/main/java/com/itant/metrowc/tab/about/AboutFragment.java
  27. 85 0
      app/src/main/java/com/itant/metrowc/tab/map/MapFragment.java
  28. 79 0
      app/src/main/java/com/itant/metrowc/tab/route/LineAdapter.java
  29. 95 0
      app/src/main/java/com/itant/metrowc/tab/route/RouteFragment.java
  30. 48 0
      app/src/main/java/com/itant/metrowc/tab/route/detail/LineDetailActivity.java
  31. 72 0
      app/src/main/java/com/itant/metrowc/tab/route/detail/StationAdapter.java
  32. 51 0
      app/src/main/java/com/itant/metrowc/tool/ActivityTool.java
  33. 35 0
      app/src/main/java/com/itant/metrowc/tool/BottomNavigationViewHelper.java
  34. 58 0
      app/src/main/java/com/itant/metrowc/tool/DonateTool.java
  35. 49 0
      app/src/main/java/com/itant/metrowc/tool/file/Assets.java
  36. 104 0
      app/src/main/java/com/itant/metrowc/tool/file/FileTool.java
  37. 360 0
      app/src/main/java/com/itant/metrowc/tool/file/IOUtils.java
  38. 164 0
      app/src/main/java/com/itant/metrowc/tool/file/StringBuilderWriter.java
  39. 140 0
      app/src/main/java/com/itant/metrowc/widget/AlignTextView.java
  40. 44 0
      app/src/main/java/com/itant/metrowc/widget/ScrollableViewPager.java
  41. 5 0
      app/src/main/res/anim/anim_slide_left_in.xml
  42. 5 0
      app/src/main/res/anim/anim_slide_left_out.xml
  43. 9 0
      app/src/main/res/anim/anim_slide_right_in.xml
  44. 9 0
      app/src/main/res/anim/anim_slide_right_out.xml
  45. 9 0
      app/src/main/res/drawable/ic_forward.xml
  46. 18 0
      app/src/main/res/drawable/selector_white_pressed.xml
  47. 6 0
      app/src/main/res/drawable/shape_round_white_solid.xml
  48. 9 0
      app/src/main/res/drawable/tab_about_24dp.xml
  49. 9 0
      app/src/main/res/drawable/tab_map_24dp.xml
  50. 13 0
      app/src/main/res/drawable/tab_route_24dp.xml
  51. 39 0
      app/src/main/res/layout/activity_main.xml
  52. 221 0
      app/src/main/res/layout/fragment_about.xml
  53. 22 0
      app/src/main/res/layout/fragment_map.xml
  54. 12 0
      app/src/main/res/layout/fragment_route.xml
  55. 42 0
      app/src/main/res/layout/item_line.xml
  56. 42 0
      app/src/main/res/layout/item_station.xml
  57. 19 0
      app/src/main/res/menu/navigation.xml
  58. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  59. BIN
      app/src/main/res/mipmap-xxhdpi/wc.png
  60. 54 0
      app/src/main/res/values/colors.xml
  61. 100 0
      app/src/main/res/values/dimens.xml
  62. 24 0
      app/src/main/res/values/strings.xml
  63. 26 0
      app/src/main/res/values/styles.xml
  64. 17 0
      app/src/test/java/com/itant/metrowc/ExampleUnitTest.java
  65. 27 0
      build.gradle
  66. 15 0
      gradle.properties
  67. BIN
      gradle/wrapper/gradle-wrapper.jar
  68. 6 0
      gradle/wrapper/gradle-wrapper.properties
  69. 172 0
      gradlew
  70. 84 0
      gradlew.bat
  71. 2 0
      settings.gradle

+ 14 - 0
.gitignore

@@ -0,0 +1,14 @@
+*.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

+ 1 - 0
app/.gitignore

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

+ 34 - 0
app/build.gradle

@@ -0,0 +1,34 @@
+apply plugin: 'com.android.application'
+
+android {
+    compileSdkVersion 28
+    defaultConfig {
+        applicationId "com.itant.metrowc"
+        minSdkVersion 17
+        targetSdkVersion 28
+        versionCode 2
+        versionName "1.1"
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+}
+
+dependencies {
+    implementation fileTree(dir: 'libs', include: ['*.jar'])
+    implementation 'com.android.support:appcompat-v7:28.0.0'
+    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
+    testImplementation 'junit:junit:4.12'
+    androidTestImplementation 'com.android.support.test:runner:1.0.2'
+    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
+
+    implementation 'com.android.support:design:27.1.1'
+    implementation 'com.android.support:cardview-v7:27.1.1'
+
+    compile 'com.umeng.sdk:common:1.5.0'
+    compile 'com.umeng.sdk:analytics:7.5.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/itant/metrowc/ExampleInstrumentedTest.java

@@ -0,0 +1,26 @@
+package com.itant.metrowc;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.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.itant.metrowc", appContext.getPackageName());
+    }
+}

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

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    package="com.itant.metrowc">
+
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+
+    <application
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:supportsRtl="true"
+        android:theme="@style/AppTheme"
+        tools:ignore="GoogleAppIndexingWarning"
+        android:name=".MetroApplication">
+        <activity android:name=".MainActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".tab.route.detail.LineDetailActivity"/>
+    </application>
+
+</manifest>

BIN
app/src/main/assets/db/metro.db


BIN
app/src/main/assets/map/img/wc.png


+ 69 - 0
app/src/main/assets/map/metro.html

@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset="utf-8" />
+        <meta name="viewport" content="initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
+        <title>地铁图展示</title>
+        <script type="text/javascript" src="https://api.map.baidu.com/api?type=subway&v=1.0&ak=9ntmUjcvSLa0KPxQNapQWlxuGVqWh4Bm"></script>
+        <script type="text/javascript">
+            // 点击了某一个地铁站
+            function clickStation(stationName){
+                jsObj.onStationClick(stationName);
+            }
+
+            // 添加马桶标志
+            function showWc(stationList){
+                var wcIcon = new BMapSub.Icon(
+                    'img/wc.png',
+                    new BMapSub.Size(60, 60)
+                );
+
+                var stationNameArray = stationList.split(',');
+                let index;
+                for (index in stationNameArray){
+                    var marker = new BMapSub.Marker(stationNameArray[index], {icon: wcIcon});
+                    subway.addMarker(marker);
+                }
+
+            }
+
+            // 清除所有马桶标志
+            function clearWc(){
+               subway.clearMarkers();
+            }
+        </script>
+        <style type="text/css">
+            #container{height:100%}
+        </style>
+    </head>
+    <body>
+        <div id="container"></div>
+        <script type="text/javascript">
+            var subwayCityName = '深圳';
+            var list = BMapSub.SubwayCitiesList;
+            var subwaycity = null;
+            for (var i = 0; i < list.length; i++) {
+                if (list[i].name === subwayCityName) {
+                    subwaycity = list[i];
+                    break;
+                }
+            }
+            // 获取北京地铁数据-初始化地铁图
+            var subway = new BMapSub.Subway('container', subwaycity.citycode);
+            subway.setZoom(0.5);
+
+            // 监听加载
+            subway.addEventListener('subwayloaded', function() {
+                // alert('地铁图加载完成');
+            });
+
+            // 点击某一个地铁站
+            subway.addEventListener('tap', function(e) {
+                clickStation(e.station.name);
+            });
+
+            // center最近地铁站
+            //subway.setCenter('白石洲');
+        </script>
+    </body>
+</html>

+ 135 - 0
app/src/main/java/com/itant/metrowc/MainActivity.java

@@ -0,0 +1,135 @@
+package com.itant.metrowc;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.support.annotation.NonNull;
+import android.support.design.widget.BottomNavigationView;
+import android.support.v4.app.Fragment;
+import android.support.v4.view.ViewPager;
+import android.support.v7.app.AppCompatActivity;
+import android.view.MenuItem;
+import android.widget.Toast;
+
+import com.itant.metrowc.adapter.FragmentPagerItemAdapter;
+import com.itant.metrowc.manager.DbManager;
+import com.itant.metrowc.manager.ThreadManager;
+import com.itant.metrowc.tab.about.AboutFragment;
+import com.itant.metrowc.tab.map.MapFragment;
+import com.itant.metrowc.tab.route.RouteFragment;
+import com.itant.metrowc.tool.BottomNavigationViewHelper;
+import com.itant.metrowc.tool.file.FileTool;
+import com.umeng.analytics.MobclickAgent;
+
+import java.io.File;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+
+public class MainActivity extends AppCompatActivity {
+    private static final int TAB_NUM = 3;
+    private ViewPager vp_main;
+    private List<Fragment> fragmentList;
+
+
+    private static final int WHAT_COPY_COMPLETE = 1;
+    private static class MainHandler extends Handler {
+        private WeakReference<MainActivity> reference;
+
+        public MainHandler(MainActivity activity) {
+            this.reference = new WeakReference<>(activity);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            super.handleMessage(msg);
+
+            MainActivity activity = reference.get();
+            if (activity != null) {
+                // 数据库文件准备完毕,开始初始化数据库的数据到内存
+                DbManager.INSTANCE.init();
+            }
+        }
+    }
+    private MainHandler mHandler = new MainHandler(this);
+
+    private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
+            = new BottomNavigationView.OnNavigationItemSelectedListener() {
+
+        @Override
+        public boolean onNavigationItemSelected(@NonNull MenuItem item) {
+            switch (item.getItemId()) {
+                case R.id.navigation_article:
+                    // 选中推荐
+                    vp_main.setCurrentItem(0);
+                    return true;
+                case R.id.navigation_photo:
+                    // 选中图片
+                    vp_main.setCurrentItem(1);
+                    return true;
+                case R.id.navigation_me:
+                    // 选中个人中心
+                    vp_main.setCurrentItem(2);
+                    return true;
+            }
+            return false;
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+
+        BottomNavigationView navigation = findViewById(R.id.navigation);
+        BottomNavigationViewHelper.disableShiftMode(navigation);
+        navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);
+
+        vp_main = findViewById(R.id.vp_main);
+        vp_main.setOffscreenPageLimit(TAB_NUM);
+        fragmentList = new ArrayList<>();
+        fragmentList.add(new MapFragment());
+        fragmentList.add(new RouteFragment());
+        fragmentList.add(new AboutFragment());
+        //fragmentList.add(new MeFragment());
+        FragmentPagerItemAdapter adapter = new FragmentPagerItemAdapter(getSupportFragmentManager(), fragmentList);
+        vp_main.setAdapter(adapter);
+        vp_main.setCurrentItem(0);
+
+        File file = new File(MetroApplication.PATH_DB);
+        if (!file.exists()) {
+            // 拷贝数据库文件到本地目录
+            ThreadManager.INSTANCE.submitTask(new Runnable() {
+                @Override
+                public void run() {
+                    FileTool.copyAssetToSDCard(MainActivity.this);
+                    mHandler.sendEmptyMessage(WHAT_COPY_COMPLETE);
+                }
+            });
+        } else {
+            mHandler.sendEmptyMessage(WHAT_COPY_COMPLETE);
+        }
+    }
+
+    private long lastMillis;
+    @Override
+    public void onBackPressed() {
+        if (System.currentTimeMillis() - lastMillis > 2500) {
+            Toast.makeText(this, getString(R.string.tips_exit), Toast.LENGTH_SHORT).show();
+            lastMillis = System.currentTimeMillis();
+            return;
+        }
+        super.onBackPressed();
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        MobclickAgent.onResume(this);
+    }
+    @Override
+    public void onPause() {
+        super.onPause();
+        MobclickAgent.onPause(this);
+    }
+}

+ 23 - 0
app/src/main/java/com/itant/metrowc/MetroApplication.java

@@ -0,0 +1,23 @@
+package com.itant.metrowc;
+
+import android.app.Application;
+import android.content.Context;
+
+import com.umeng.commonsdk.UMConfigure;
+
+import java.io.File;
+
+public class MetroApplication extends Application {
+    public static String PATH_DB;
+    public static Context mContext;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+
+        UMConfigure.init(this, "5db871f94ca35797b7000840", "all", UMConfigure.DEVICE_TYPE_PHONE, "");
+        mContext = this;
+        File file = new File(getFilesDir(), "db/metro.db");
+        PATH_DB = file.getAbsolutePath();
+    }
+}

+ 31 - 0
app/src/main/java/com/itant/metrowc/adapter/FragmentPagerItemAdapter.java

@@ -0,0 +1,31 @@
+package com.itant.metrowc.adapter;
+
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentPagerAdapter;
+
+import java.util.List;
+
+/**
+ * Created by Jason on 2018/5/19.
+ */
+
+public class FragmentPagerItemAdapter extends FragmentPagerAdapter {
+
+    private List<Fragment> fragmentList;
+
+    public FragmentPagerItemAdapter(FragmentManager fm, List<Fragment> fragmentList) {
+        super(fm);
+        this.fragmentList = fragmentList;
+    }
+
+    @Override
+    public Fragment getItem(int position) {
+        return fragmentList.get(position);
+    }
+
+    @Override
+    public int getCount() {
+        return fragmentList.size();
+    }
+}

+ 32 - 0
app/src/main/java/com/itant/metrowc/base/BaseFragment.java

@@ -0,0 +1,32 @@
+package com.itant.metrowc.base;
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+public abstract class BaseFragment extends Fragment {
+
+    private View mView;
+
+    public abstract int getLayoutId();
+
+    public abstract void initViews(View view);
+
+    @Nullable
+    @Override
+    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+        if (mView == null) {
+            mView = inflater.inflate(getLayoutId(), container, false);
+            initViews(mView);
+        }
+        return mView;
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+    }
+}

+ 17 - 0
app/src/main/java/com/itant/metrowc/bean/City.java

@@ -0,0 +1,17 @@
+package com.itant.metrowc.bean;
+
+/**
+ * 城市
+ */
+public class City {
+    private int cityId;
+    private String cityName;
+
+    private int countryId;
+    private String countryName;
+
+    private String cityLogo;
+    private String description;
+
+
+}

+ 12 - 0
app/src/main/java/com/itant/metrowc/bean/Country.java

@@ -0,0 +1,12 @@
+package com.itant.metrowc.bean;
+
+/**
+ * 国家
+ */
+public class Country {
+    private int countryId;
+    private String countryName;
+
+    private String logo;
+    private String description;
+}

+ 20 - 0
app/src/main/java/com/itant/metrowc/bean/Exit.java

@@ -0,0 +1,20 @@
+package com.itant.metrowc.bean;
+
+/**
+ * 出口
+ */
+public class Exit {
+    private int exitId;
+    private String exitName;
+    private boolean hasWc;
+    private boolean isSceneExit;
+    private String sceneName;
+    private String description;
+
+    private int stationId;
+    private int lineId;
+    private int cityId;
+    private int countryId;
+
+
+}

+ 90 - 0
app/src/main/java/com/itant/metrowc/bean/Line.java

@@ -0,0 +1,90 @@
+package com.itant.metrowc.bean;
+
+/**
+ *  线路
+ */
+public class Line {
+    private int lineId;
+    private String lineName;
+    private String lineNickName;
+    private String description;
+
+    private int countryId;
+    private String countryName;
+    private int cityId;
+    private String cityName;
+    private String color;
+
+
+    public int getLineId() {
+        return lineId;
+    }
+
+    public void setLineId(int lineId) {
+        this.lineId = lineId;
+    }
+
+    public String getLineName() {
+        return lineName;
+    }
+
+    public void setLineName(String lineName) {
+        this.lineName = lineName;
+    }
+
+    public String getLineNickName() {
+        return lineNickName;
+    }
+
+    public void setLineNickName(String lineNickName) {
+        this.lineNickName = lineNickName;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public int getCountryId() {
+        return countryId;
+    }
+
+    public void setCountryId(int countryId) {
+        this.countryId = countryId;
+    }
+
+    public String getCountryName() {
+        return countryName;
+    }
+
+    public void setCountryName(String countryName) {
+        this.countryName = countryName;
+    }
+
+    public int getCityId() {
+        return cityId;
+    }
+
+    public void setCityId(int cityId) {
+        this.cityId = cityId;
+    }
+
+    public String getCityName() {
+        return cityName;
+    }
+
+    public void setCityName(String cityName) {
+        this.cityName = cityName;
+    }
+
+    public String getColor() {
+        return color;
+    }
+
+    public void setColor(String color) {
+        this.color = color;
+    }
+}

+ 269 - 0
app/src/main/java/com/itant/metrowc/bean/Station.java

@@ -0,0 +1,269 @@
+package com.itant.metrowc.bean;
+
+/**
+ * 地铁站
+ */
+public class Station {
+    // 站id
+    private int stationId;
+
+    // 站名
+    private String stationName;
+
+    // 英文名
+    private String englishName;
+
+    // 国家码
+    private int countryId;
+
+    // 国家名
+    private String countryName;
+
+    // 城市码
+    private int cityId;
+
+    // 城市名
+    private String cityName;
+
+    private int lineId;
+
+    // 所属线的名称,如1号线
+    private String lineName;
+
+    // 所属线的别称,如罗宝线
+    private String lineNickName;
+
+    // 是否为交汇处
+    private boolean isCrossing;
+
+    // 是否有厕所
+    private boolean hasWc;
+
+    // 厕所所在的出口名
+    private String exitNameOfWc;
+
+    // 是否景点站
+    private boolean isScene;
+
+    // 景点所在的出口名
+    private String exitNameOfScene;
+
+    // 景点名
+    private String sceneName;
+
+    // 出口数量
+    private int exitNumbers;
+
+    // 是否被选中
+    private boolean isFocus;
+
+    // 该地铁站的描述
+    private String description;
+
+    private String direction1;
+    private String first1;
+    private String last1;
+    private String direction2;
+    private String first2;
+    private String last2;
+
+    public int getStationId() {
+        return stationId;
+    }
+
+    public void setStationId(int stationId) {
+        this.stationId = stationId;
+    }
+
+    public String getStationName() {
+        return stationName;
+    }
+
+    public void setStationName(String stationName) {
+        this.stationName = stationName;
+    }
+
+    public String getEnglishName() {
+        return englishName;
+    }
+
+    public void setEnglishName(String englishName) {
+        this.englishName = englishName;
+    }
+
+    public int getCountryId() {
+        return countryId;
+    }
+
+    public void setCountryId(int countryId) {
+        this.countryId = countryId;
+    }
+
+    public String getCountryName() {
+        return countryName;
+    }
+
+    public void setCountryName(String countryName) {
+        this.countryName = countryName;
+    }
+
+    public int getCityId() {
+        return cityId;
+    }
+
+    public void setCityId(int cityId) {
+        this.cityId = cityId;
+    }
+
+    public String getCityName() {
+        return cityName;
+    }
+
+    public void setCityName(String cityName) {
+        this.cityName = cityName;
+    }
+
+    public int getLineId() {
+        return lineId;
+    }
+
+    public void setLineId(int lineId) {
+        this.lineId = lineId;
+    }
+
+    public String getLineName() {
+        return lineName;
+    }
+
+    public void setLineName(String lineName) {
+        this.lineName = lineName;
+    }
+
+    public String getLineNickName() {
+        return lineNickName;
+    }
+
+    public void setLineNickName(String lineNickName) {
+        this.lineNickName = lineNickName;
+    }
+
+    public boolean isCrossing() {
+        return isCrossing;
+    }
+
+    public void setCrossing(boolean crossing) {
+        isCrossing = crossing;
+    }
+
+    public boolean isHasWc() {
+        return hasWc;
+    }
+
+    public void setHasWc(boolean hasWc) {
+        this.hasWc = hasWc;
+    }
+
+    public String getExitNameOfWc() {
+        return exitNameOfWc;
+    }
+
+    public void setExitNameOfWc(String exitNameOfWc) {
+        this.exitNameOfWc = exitNameOfWc;
+    }
+
+    public boolean isScene() {
+        return isScene;
+    }
+
+    public void setScene(boolean scene) {
+        isScene = scene;
+    }
+
+    public String getExitNameOfScene() {
+        return exitNameOfScene;
+    }
+
+    public void setExitNameOfScene(String exitNameOfScene) {
+        this.exitNameOfScene = exitNameOfScene;
+    }
+
+    public String getSceneName() {
+        return sceneName;
+    }
+
+    public void setSceneName(String sceneName) {
+        this.sceneName = sceneName;
+    }
+
+    public int getExitNumbers() {
+        return exitNumbers;
+    }
+
+    public void setExitNumbers(int exitNumbers) {
+        this.exitNumbers = exitNumbers;
+    }
+
+    public boolean isFocus() {
+        return isFocus;
+    }
+
+    public void setFocus(boolean focus) {
+        isFocus = focus;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public String getDirection1() {
+        return direction1;
+    }
+
+    public void setDirection1(String direction1) {
+        this.direction1 = direction1;
+    }
+
+    public String getFirst1() {
+        return first1;
+    }
+
+    public void setFirst1(String first1) {
+        this.first1 = first1;
+    }
+
+    public String getLast1() {
+        return last1;
+    }
+
+    public void setLast1(String last1) {
+        this.last1 = last1;
+    }
+
+    public String getDirection2() {
+        return direction2;
+    }
+
+    public void setDirection2(String direction2) {
+        this.direction2 = direction2;
+    }
+
+    public String getFirst2() {
+        return first2;
+    }
+
+    public void setFirst2(String first2) {
+        this.first2 = first2;
+    }
+
+    public String getLast2() {
+        return last2;
+    }
+
+    public void setLast2(String last2) {
+        this.last2 = last2;
+    }
+}

+ 85 - 0
app/src/main/java/com/itant/metrowc/manager/ActivityManager.java

@@ -0,0 +1,85 @@
+package com.itant.metrowc.manager;
+
+import android.app.Activity;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Created by Jason on 2018/8/26.
+ */
+
+public class ActivityManager {
+    private static ActivityManager activityManager;
+    private Set<Activity> activitySet;
+
+    private boolean isLoginExist;
+
+    private ActivityManager() {
+        activitySet = new HashSet<>();
+    }
+
+    public static ActivityManager getInstance() {
+        if (activityManager == null) {
+            initManager();
+        }
+
+        return activityManager;
+    }
+
+    private static synchronized void initManager() {
+        if (activityManager == null) {
+            activityManager = new ActivityManager();
+        }
+    }
+
+    public void addActivity(Activity activity) {
+        if (!activitySet.contains(activity)) {
+            activitySet.add(activity);
+        }
+    }
+
+    public void removeActivity(Activity activity) {
+        activitySet.remove(activity);
+    }
+
+    public void clearActivity() {
+        if (activitySet == null || activitySet.size() == 0) {
+            return;
+        }
+        for (Activity activity : activitySet) {
+            activity.finish();
+        }
+        activitySet.clear();
+    }
+
+    public void clearActivityExcept(Activity except) {
+        if (activitySet == null || activitySet.size() == 0) {
+            return;
+        }
+        for (Activity activity : activitySet) {
+            if (activity != except) {
+                activity.finish();
+            }
+        }
+    }
+
+    public void clearActivityExcept(Class except) {
+        if (activitySet == null || activitySet.size() == 0) {
+            return;
+        }
+        for (Activity activity : activitySet) {
+            if (activity.getClass() != except) {
+                activity.finish();
+            }
+        }
+    }
+
+    public boolean isLoginExist() {
+        return isLoginExist;
+    }
+
+    public void setLoginExist(boolean loginExist) {
+        isLoginExist = loginExist;
+    }
+}

+ 22 - 0
app/src/main/java/com/itant/metrowc/manager/DatabaseManager.java

@@ -0,0 +1,22 @@
+package com.itant.metrowc.manager;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.support.annotation.Nullable;
+
+public class DatabaseManager extends SQLiteOpenHelper {
+    public DatabaseManager(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) {
+        super(context, name, factory, version);
+    }
+
+    @Override
+    public void onCreate(SQLiteDatabase db) {
+
+    }
+
+    @Override
+    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+
+    }
+}

+ 133 - 0
app/src/main/java/com/itant/metrowc/manager/DbManager.java

@@ -0,0 +1,133 @@
+package com.itant.metrowc.manager;
+
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.text.TextUtils;
+
+import com.itant.metrowc.MetroApplication;
+import com.itant.metrowc.bean.Station;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+public enum DbManager {
+    INSTANCE;
+
+    private SQLiteDatabase mDb;
+    private CopyOnWriteArrayList<Station> mStationList;
+    private String mHasWcCityNameArray;
+    public synchronized void init() {
+        File dbFile = new File(MetroApplication.PATH_DB);
+        if (!dbFile.exists()) {
+            return;
+        }
+
+        mDb = SQLiteDatabase.openOrCreateDatabase(dbFile, null);
+        mStationList = new CopyOnWriteArrayList<>();
+        // 由于Java传数组或对象给JavaScript有困难,所以通过转换为字符串或者JSON数据传过去
+        StringBuilder builder = new StringBuilder();
+        builder.append("'");
+
+        //String sql = "select * from greens where classify = '家常菜'";
+        //Cursor cursor = db.rawQuery(sql, null);
+        Cursor cursor = mDb.query("station", new String[]{
+                "stationId", "stationName", "englishName", "countryId", "countryName", "cityId",
+        "cityName", "lineId", "lineName", "isCrossing", "hasWc", "exitNameOfWc", "isScene",
+        "exitNameOfScene", "direction1", "first1", "last1", "direction2", "first2", "last2"}, null, null, null, null, null);
+        //利用游标遍历所有数据对象
+        //为了显示全部,把所有对象连接起来,放到TextView中
+        while(cursor.moveToNext()){
+            Station station = new Station();
+            int stationId = cursor.getInt(cursor.getColumnIndex("stationId"));
+            station.setStationId(stationId);
+            String stationName = cursor.getString(cursor.getColumnIndex("stationName"));
+            station.setStationName(stationName);
+            String cityName = cursor.getString(cursor.getColumnIndex("cityName"));
+            station.setCityName(cityName);
+            int lineId = cursor.getInt(cursor.getColumnIndex("lineId"));
+            station.setLineId(lineId);
+            String lineName = cursor.getString(cursor.getColumnIndex("lineName"));
+            station.setLineName(lineName);
+            int isCrossing = cursor.getInt(cursor.getColumnIndex("isCrossing"));
+            station.setCrossing(isCrossing == 1);
+            int hasWc = cursor.getInt(cursor.getColumnIndex("hasWc"));
+            station.setHasWc(hasWc == 1);
+            String exitNameOfWc = cursor.getString(cursor.getColumnIndex("exitNameOfWc"));
+            station.setExitNameOfWc(exitNameOfWc);
+            int isScene = cursor.getInt(cursor.getColumnIndex("isScene"));
+            station.setScene(isScene == 1);
+            String exitNameOfScene = cursor.getString(cursor.getColumnIndex("exitNameOfScene"));
+            station.setExitNameOfScene(exitNameOfScene);
+            String direction1 = cursor.getString(cursor.getColumnIndex("direction1"));
+            station.setDirection1(direction1);
+            String first1 = cursor.getString(cursor.getColumnIndex("first1"));
+            station.setFirst1(first1);
+            String last1 = cursor.getString(cursor.getColumnIndex("last1"));
+            station.setLast1(last1);
+            String direction2 = cursor.getString(cursor.getColumnIndex("direction2"));
+            station.setDirection2(direction2);
+            String first2 = cursor.getString(cursor.getColumnIndex("first2"));
+            station.setFirst2(first2);
+            String last2 = cursor.getString(cursor.getColumnIndex("last2"));
+            station.setLast2(last2);
+
+            mStationList.add(station);
+            if (hasWc == 1) {
+                builder.append(stationName).append(",");
+            }
+        }
+        // 关闭游标,释放资源
+        cursor.close();
+
+        //mHasWcCityNameArray = new String[hasWcCityNameList.size()];
+        //hasWcCityNameList.toArray(mHasWcCityNameArray);
+        builder.deleteCharAt(builder.length()-1);
+        builder.append("'");
+        mHasWcCityNameArray = builder.toString();
+    }
+
+    public Station getStationByName(String stationName) {
+        if (mStationList == null || mStationList.size() == 0) {
+            return null;
+        }
+
+        for (Station station : mStationList) {
+            if (TextUtils.equals(stationName, station.getStationName())) {
+                return station;
+            }
+        }
+        return null;
+    }
+
+    public CopyOnWriteArrayList<Station> getStationList() {
+        return mStationList;
+    }
+
+    public void setStationList(CopyOnWriteArrayList<Station> mStationList) {
+        this.mStationList = mStationList;
+    }
+
+    public String getHasWcCityNameArray() {
+        return mHasWcCityNameArray;
+    }
+
+    public List<Station> getStationListByLineId(int lineId) {
+        if (lineId < 0) {
+            return null;
+        }
+
+        if (mStationList == null) {
+            return null;
+        }
+
+        List<Station> stationList = new ArrayList<>();
+        for (Station station : mStationList) {
+            if (station.getLineId() == lineId) {
+                stationList.add(station);
+            }
+        }
+        return stationList;
+    }
+}

+ 80 - 0
app/src/main/java/com/itant/metrowc/manager/DialogManager.java

@@ -0,0 +1,80 @@
+package com.itant.metrowc.manager;
+
+import android.app.Activity;
+import android.content.DialogInterface;
+import android.support.v7.app.AlertDialog;
+import android.text.TextUtils;
+
+import com.itant.metrowc.R;
+import com.itant.metrowc.bean.Station;
+
+/**
+ * Created by Jason on 2019/10/25.
+ */
+
+public enum DialogManager {
+    INSTANCE;
+
+    private AlertDialog dialog;
+    public void showWcDialog(Activity activity, Station station) {
+        AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+        builder.setCancelable(false);
+
+        builder.setTitle(station.getStationName());
+        if (station.isHasWc()) {
+            String content = String.format(activity.getString(R.string.tips_wc_position), station.getExitNameOfWc());
+            builder.setMessage(content);
+        } else {
+            builder.setMessage(activity.getString(R.string.tips_no_wc));
+        }
+        builder.setPositiveButton(activity.getString(R.string.confirm), new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                hideDialog();
+            }
+        });
+        dialog = builder.create();
+        dialog.setCanceledOnTouchOutside(false);
+        dialog.show();
+    }
+
+    public void showTimeDialog(Activity activity, Station station) {
+        AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+        builder.setCancelable(false);
+
+        builder.setTitle(station.getLineName() + station.getStationName());
+        StringBuilder contentBuilder = new StringBuilder();
+        if (station.isHasWc()) {
+            String content = String.format(activity.getString(R.string.tips_wc_position), station.getExitNameOfWc());
+            contentBuilder.append(content).append("\n");
+        } else {
+            contentBuilder.append(activity.getString(R.string.tips_no_wc)).append("\n");
+        }
+
+        if (!TextUtils.isEmpty(station.getDirection1())) {
+            contentBuilder.append(station.getDirection1()).append(":").append(station.getFirst1()).append(" -- ").append(station.getLast1()).append("\n");
+        }
+
+        if (!TextUtils.isEmpty(station.getDirection2())) {
+            contentBuilder.append(station.getDirection2()).append(":").append(station.getFirst2()).append(" -- ").append(station.getLast2()).append("\n");
+        }
+
+        builder.setMessage(contentBuilder.toString());
+        builder.setPositiveButton(activity.getString(R.string.confirm), new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                hideDialog();
+            }
+        });
+        dialog = builder.create();
+        dialog.setCanceledOnTouchOutside(false);
+        dialog.show();
+    }
+
+    public void hideDialog() {
+        if (dialog != null) {
+            dialog.cancel();
+            dialog = null;
+        }
+    }
+}

+ 25 - 0
app/src/main/java/com/itant/metrowc/manager/StationManager.java

@@ -0,0 +1,25 @@
+package com.itant.metrowc.manager;
+
+import com.itant.metrowc.bean.Station;
+
+import java.util.List;
+
+public enum StationManager {
+    INSTANCE;
+
+    public void init() {
+
+    }
+
+    // 有厕所的车站名集合
+    private List<String> hasWcStationList;
+
+    public List<String> getHasWcStationList() {
+        return hasWcStationList;
+    }
+
+    public void setHasWcStationList(List<String> hasWcStationList) {
+        this.hasWcStationList = hasWcStationList;
+    }
+
+}

+ 26 - 0
app/src/main/java/com/itant/metrowc/manager/ThreadManager.java

@@ -0,0 +1,26 @@
+package com.itant.metrowc.manager;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public enum ThreadManager {
+    INSTANCE;
+
+    private ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
+
+    public void submitTask(Runnable task) {
+        if (mExecutorService != null) {
+            mExecutorService.submit(task);
+        }
+    }
+
+    public void shutDownAllTask() {
+        onDestroy();
+        mExecutorService = Executors.newSingleThreadExecutor();
+    }
+
+    public void onDestroy() {
+        mExecutorService.shutdownNow();
+        mExecutorService = null;
+    }
+}

+ 56 - 0
app/src/main/java/com/itant/metrowc/manager/ToastManager.java

@@ -0,0 +1,56 @@
+package com.itant.metrowc.manager;
+
+import android.content.Context;
+import android.widget.Toast;
+
+import com.itant.metrowc.MetroApplication;
+
+
+/**
+ * Created by Jason on 2018/8/31.
+ */
+
+public class ToastManager {
+
+    private static ToastManager manager;
+    private long lastShortToastMillis = 0;
+
+    private ToastManager() {
+
+    }
+
+    public static ToastManager getInstance() {
+        if (manager == null) {
+            init();
+        }
+        return manager;
+    }
+
+    private static synchronized void init() {
+        if (manager == null) {
+            manager = new ToastManager();
+        }
+    }
+
+    /**
+     * 不要频繁Toast
+     * @param message 内容
+     */
+    public void toastShort(String message) {
+        if (System.currentTimeMillis() - lastShortToastMillis > 2000) {
+            Toast.makeText(MetroApplication.mContext, message, Toast.LENGTH_SHORT).show();
+            //toast.setText(message);
+            //toast.show();
+        }
+        lastShortToastMillis = System.currentTimeMillis();
+    }
+
+    public void toastShort(String message, Context context) {
+        if (System.currentTimeMillis() - lastShortToastMillis > 2000) {
+            Toast toast = Toast.makeText(context, null, Toast.LENGTH_SHORT);
+            toast.setText(message);
+            toast.show();
+        }
+        lastShortToastMillis = System.currentTimeMillis();
+    }
+}

+ 48 - 0
app/src/main/java/com/itant/metrowc/tab/about/AboutFragment.java

@@ -0,0 +1,48 @@
+package com.itant.metrowc.tab.about;
+
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.view.View;
+import android.widget.TextView;
+
+import com.itant.metrowc.R;
+import com.itant.metrowc.base.BaseFragment;
+import com.itant.metrowc.tool.DonateTool;
+
+public class AboutFragment extends BaseFragment implements View.OnClickListener {
+
+    @Override
+    public int getLayoutId() {
+        return R.layout.fragment_about;
+    }
+
+    @Override
+    public void initViews(View view) {
+        TextView tv_desc = view.findViewById(R.id.tv_desc);
+        String desc = getResources().getString(R.string.about);
+        tv_desc.setText(desc);
+        tv_desc.setOnClickListener(this);
+
+        TextView tv_app_name = view.findViewById(R.id.tv_app_name);
+        try {
+            PackageManager manager = getActivity().getPackageManager();
+            // 0标识获取基本信息
+            PackageInfo packageInfo = manager.getPackageInfo(getActivity().getPackageName(), 0);
+            tv_app_name.setText(getString(R.string.app_name) + " v" + packageInfo.versionName);
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+        }
+
+        view.findViewById(R.id.ll_donate).setOnClickListener(this);
+    }
+
+    @Override
+    public void onClick(View v) {
+        switch (v.getId()) {
+            case R.id.ll_donate:
+                // 捐助,支付宝自定义输入
+                DonateTool.openAlipayPayPage(getActivity(), "HTTPS://QR.ALIPAY.COM/FKX05955OEVVEN0DMQXO49");
+                break;
+        }
+    }
+}

+ 85 - 0
app/src/main/java/com/itant/metrowc/tab/map/MapFragment.java

@@ -0,0 +1,85 @@
+package com.itant.metrowc.tab.map;
+
+import android.view.View;
+import android.webkit.JavascriptInterface;
+import android.webkit.WebChromeClient;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.widget.CompoundButton;
+import android.widget.Switch;
+
+import com.itant.metrowc.R;
+import com.itant.metrowc.base.BaseFragment;
+import com.itant.metrowc.bean.Station;
+import com.itant.metrowc.manager.DbManager;
+import com.itant.metrowc.manager.DialogManager;
+
+public class MapFragment extends BaseFragment {
+    private WebView wv_map;
+
+    @Override
+    public int getLayoutId() {
+        return R.layout.fragment_map;
+    }
+
+    @Override
+    public void initViews(View view) {
+        wv_map = view.findViewById(R.id.wv_map);
+        WebSettings settings = wv_map.getSettings();
+        // 显示完整网页
+        settings.setUseWideViewPort(true);
+        settings.setLoadWithOverviewMode(true);
+        settings.setJavaScriptEnabled(true);
+        // 显示放大缩小 controller
+        settings.setBuiltInZoomControls(false);
+        // 可以缩放
+        settings.setSupportZoom(true);
+        // 默认缩放模式
+        settings.setDefaultZoom(WebSettings.ZoomDensity.CLOSE);
+        wv_map.setWebChromeClient(new WebChromeClient());
+        wv_map.setWebViewClient(new WebViewClient());
+        settings.setDomStorageEnabled(true);
+        settings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);
+
+        // 设置可以访问文件
+        settings.setAllowFileAccess(true);
+        settings.setAllowFileAccessFromFileURLs(true);
+        settings.setAllowFileAccessFromFileURLs(true);
+        settings.setAllowContentAccess(true);
+        settings.setDomStorageEnabled(true);
+
+        //JSInterface类对象映射到js的jsObj对象
+        wv_map.addJavascriptInterface(new JSInterface(), "jsObj");
+        wv_map.loadUrl("file:///android_asset/map/metro.html");
+
+        Switch switch_show_wc = view.findViewById(R.id.switch_show_wc);
+        switch_show_wc.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+            @Override
+            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                showOrHideWc(isChecked);
+            }
+        });
+    }
+
+    private void showOrHideWc(boolean isChecked) {
+        if (isChecked) {
+            wv_map.loadUrl("javascript:showWc(" + DbManager.INSTANCE.getHasWcCityNameArray() + ")");
+        } else {
+            wv_map.loadUrl("javascript:clearWc()");
+        }
+    }
+
+    public class JSInterface {
+        // 定义JS需要调用的方法
+        // 被JS调用的方法必须加入@JavascriptInterface注解
+        @JavascriptInterface
+        public void onStationClick(String stationName) {
+            Station station = DbManager.INSTANCE.getStationByName(stationName);
+            if (station == null) {
+                return;
+            }
+            DialogManager.INSTANCE.showWcDialog(getActivity(), station);
+        }
+    }
+}

+ 79 - 0
app/src/main/java/com/itant/metrowc/tab/route/LineAdapter.java

@@ -0,0 +1,79 @@
+package com.itant.metrowc.tab.route;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.itant.metrowc.R;
+import com.itant.metrowc.bean.Line;
+import com.itant.metrowc.tab.route.detail.LineDetailActivity;
+
+import java.util.List;
+
+public class LineAdapter extends BaseAdapter {
+    private List<Line> lineList;
+    private Context mContext;
+
+    public LineAdapter(Context context, List<Line> lineList) {
+        mContext = context;
+        this.lineList = lineList;
+    }
+
+    @Override
+    public int getCount() {
+        return lineList == null ? 0 : lineList.size();
+    }
+
+    @Override
+    public Object getItem(int position) {
+        return lineList.get(position);
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return position;
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        if (convertView == null) {
+            convertView = LayoutInflater.from(mContext).inflate(R.layout.item_line, null);
+            mHolder = new ViewHolder();
+            mHolder.tv_line_name = convertView.findViewById(R.id.tv_line_name);
+            mHolder.tv_description = convertView.findViewById(R.id.tv_description);
+            mHolder.ll_line_bg = convertView.findViewById(R.id.ll_line_bg);
+            mHolder.ll_line_item = convertView.findViewById(R.id.ll_line_item);
+            convertView.setTag(mHolder);
+        } else {
+            mHolder = (ViewHolder) convertView.getTag();
+        }
+
+        final Line line = lineList.get(position);
+        mHolder.tv_line_name.setText(line.getLineName());
+        mHolder.tv_description.setText(line.getDescription());
+        mHolder.ll_line_bg.setBackgroundColor(Color.parseColor(line.getColor()));
+        mHolder.ll_line_item.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Intent intent = new Intent(mContext, LineDetailActivity.class);
+                intent.putExtra(LineDetailActivity.KEY_LINE_ID, line.getLineId());
+                mContext.startActivity(intent);
+            }
+        });
+        return convertView;
+    }
+
+    private ViewHolder mHolder;
+    private static class ViewHolder {
+        TextView tv_line_name;
+        TextView tv_description;
+        LinearLayout ll_line_bg;
+        LinearLayout ll_line_item;
+    }
+}

+ 95 - 0
app/src/main/java/com/itant/metrowc/tab/route/RouteFragment.java

@@ -0,0 +1,95 @@
+package com.itant.metrowc.tab.route;
+
+import android.view.View;
+import android.widget.ListView;
+
+import com.itant.metrowc.R;
+import com.itant.metrowc.base.BaseFragment;
+import com.itant.metrowc.bean.Line;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class RouteFragment extends BaseFragment {
+    private ListView lv_line;
+    private LineAdapter mAdapter;
+    private List<Line> mLineList;
+
+    @Override
+    public int getLayoutId() {
+        return R.layout.fragment_route;
+    }
+
+    @Override
+    public void initViews(View view) {
+        lv_line = view.findViewById(R.id.lv_line);
+        mLineList = new ArrayList<>();
+        mAdapter = new LineAdapter(getActivity(), mLineList);
+        lv_line.setAdapter(mAdapter);
+
+        initData();
+    }
+
+    private void initData() {
+        Line line1 = new Line();
+        line1.setLineId(1);
+        line1.setLineName("1号线");
+        line1.setDescription("机场东-罗湖");
+        line1.setColor(getString(R.string.line1));
+
+        Line line2 = new Line();
+        line2.setLineId(2);
+        line2.setLineName("2号线");
+        line2.setDescription("赤湾-新秀");
+        line2.setColor(getString(R.string.line2));
+
+        Line line3 = new Line();
+        line3.setLineId(3);
+        line3.setLineName("3号线");
+        line3.setDescription("益田-双龙");
+        line3.setColor(getString(R.string.line3));
+
+
+        Line line4 = new Line();
+        line4.setLineId(4);
+        line4.setLineName("4号线");
+        line4.setDescription("福田口岸-清湖");
+        line4.setColor(getString(R.string.line4));
+
+
+        Line line5 = new Line();
+        line5.setLineId(5);
+        line5.setLineName("5号线");
+        line5.setDescription("赤湾-黄贝岭");
+        line5.setColor(getString(R.string.line5));
+
+        Line line7 = new Line();
+        line7.setLineId(6);
+        line7.setLineName("7号线");
+        line7.setDescription("西丽湖-太安");
+        line7.setColor(getString(R.string.line7));
+
+        Line line9 = new Line();
+        line9.setLineId(7);
+        line9.setLineName("9号线");
+        line9.setDescription("文锦-红树湾南");
+        line9.setColor(getString(R.string.line9));
+
+
+        Line line11 = new Line();
+        line11.setLineId(8);
+        line11.setLineName("11号线");
+        line11.setDescription("碧头-福田");
+        line11.setColor(getString(R.string.line11));
+
+        mLineList.add(line1);
+        mLineList.add(line2);
+        mLineList.add(line3);
+        mLineList.add(line4);
+        mLineList.add(line5);
+        mLineList.add(line7);
+        mLineList.add(line9);
+        mLineList.add(line11);
+        mAdapter.notifyDataSetChanged();
+    }
+}

+ 48 - 0
app/src/main/java/com/itant/metrowc/tab/route/detail/LineDetailActivity.java

@@ -0,0 +1,48 @@
+package com.itant.metrowc.tab.route.detail;
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v7.app.AppCompatActivity;
+import android.widget.ListView;
+
+import com.itant.metrowc.R;
+import com.itant.metrowc.bean.Station;
+import com.itant.metrowc.manager.DbManager;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class LineDetailActivity extends AppCompatActivity {
+    public static final String KEY_LINE_ID = "line_id";
+
+    private ListView lv_line;
+    private StationAdapter mAdapter;
+    private List<Station> mStationList;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.fragment_route);
+
+        lv_line = findViewById(R.id.lv_line);
+        mStationList = new ArrayList<>();
+        mAdapter = new StationAdapter(this, mStationList);
+        lv_line.setAdapter(mAdapter);
+
+        initData();
+    }
+
+    private void initData() {
+        int lineId = getIntent().getIntExtra(KEY_LINE_ID, -1);
+        if (lineId == -1) {
+            return;
+        }
+
+        List<Station> currentLineStationList = DbManager.INSTANCE.getStationListByLineId(lineId);
+        if (currentLineStationList == null) {
+            return;
+        }
+        mStationList.addAll(currentLineStationList);
+        mAdapter.notifyDataSetChanged();
+    }
+}

+ 72 - 0
app/src/main/java/com/itant/metrowc/tab/route/detail/StationAdapter.java

@@ -0,0 +1,72 @@
+package com.itant.metrowc.tab.route.detail;
+
+import android.app.Activity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.itant.metrowc.R;
+import com.itant.metrowc.bean.Station;
+import com.itant.metrowc.manager.DialogManager;
+
+import java.util.List;
+
+public class StationAdapter extends BaseAdapter {
+    private List<Station> stationList;
+    private Activity mContext;
+
+    public StationAdapter(Activity context, List<Station> stationList) {
+        mContext = context;
+        this.stationList = stationList;
+    }
+
+    @Override
+    public int getCount() {
+        return stationList == null ? 0 : stationList.size();
+    }
+
+    @Override
+    public Object getItem(int position) {
+        return stationList.get(position);
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return position;
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        if (convertView == null) {
+            convertView = LayoutInflater.from(mContext).inflate(R.layout.item_station, null);
+            mHolder = new ViewHolder();
+            mHolder.tv_line_name = convertView.findViewById(R.id.tv_line_name);
+            mHolder.tv_description = convertView.findViewById(R.id.tv_description);
+            mHolder.ll_line_item = convertView.findViewById(R.id.ll_line_item);
+            convertView.setTag(mHolder);
+        } else {
+            mHolder = (ViewHolder) convertView.getTag();
+        }
+
+        final Station station = stationList.get(position);
+        mHolder.tv_line_name.setText(station.getStationName());
+        //mHolder.tv_description.setText(station.getDescription());
+        mHolder.ll_line_item.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                DialogManager.INSTANCE.showTimeDialog(mContext, station);
+            }
+        });
+        return convertView;
+    }
+
+    private ViewHolder mHolder;
+    private static class ViewHolder {
+        TextView tv_line_name;
+        TextView tv_description;
+        LinearLayout ll_line_item;
+    }
+}

+ 51 - 0
app/src/main/java/com/itant/metrowc/tool/ActivityTool.java

@@ -0,0 +1,51 @@
+package com.itant.metrowc.tool;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+import com.itant.metrowc.R;
+
+
+/**
+ * Created by iTant on 2017/4/8.
+ */
+
+public class ActivityTool {
+
+    /**
+     * Activity从右边往左边进,配合BaseActivity.finish()方法实现从左边往右边销毁
+     * @param activity
+     * @param intent
+     */
+    public static void startActivity(Activity activity, Intent intent) {
+        activity.startActivity(intent);
+        activity.overridePendingTransition(R.anim.anim_slide_right_in, R.anim.anim_slide_left_out);
+    }
+
+    /**
+     * Activity从右边往左边进,配合BaseActivity.finish()方法实现从左边往右边销毁
+     * @param activity
+     */
+    public static void startActivity(Activity activity, Class clazz) {
+        activity.startActivity(new Intent(activity, clazz));
+        activity.overridePendingTransition(R.anim.anim_slide_right_in, R.anim.anim_slide_left_out);
+    }
+
+    /**
+     * Activity从右边往左边进,配合BaseActivity.finish()方法实现从左边往右边销毁
+     * @param activity
+     * @param intent
+     */
+    @SuppressLint("NewApi")
+    public static void startActivity(Activity activity, Intent intent, Bundle bundle) {
+        activity.startActivity(intent, bundle);
+        activity.overridePendingTransition(R.anim.anim_slide_right_in, R.anim.anim_slide_left_out);
+    }
+
+    public static void startActivityForResult(Activity activity, Intent intent, int requestCode) {
+        activity.startActivityForResult(intent, requestCode);
+        activity.overridePendingTransition(R.anim.anim_slide_right_in, R.anim.anim_slide_left_out);
+    }
+}

+ 35 - 0
app/src/main/java/com/itant/metrowc/tool/BottomNavigationViewHelper.java

@@ -0,0 +1,35 @@
+package com.itant.metrowc.tool;
+
+import android.support.design.internal.BottomNavigationItemView;
+import android.support.design.internal.BottomNavigationMenuView;
+import android.support.design.widget.BottomNavigationView;
+import android.util.Log;
+
+import java.lang.reflect.Field;
+
+public class BottomNavigationViewHelper {
+
+    /**
+     * 当Tab个数大于3时,去掉动画
+     * @param view
+     */
+    public static void disableShiftMode(BottomNavigationView view) {
+        BottomNavigationMenuView menuView = (BottomNavigationMenuView) view.getChildAt(0);
+        try {
+            Field shiftingMode = menuView.getClass().getDeclaredField("mShiftingMode");
+            shiftingMode.setAccessible(true);
+            shiftingMode.setBoolean(menuView, false);
+            shiftingMode.setAccessible(false);
+            for (int i = 0; i < menuView.getChildCount(); i++) {
+                BottomNavigationItemView item = (BottomNavigationItemView) menuView.getChildAt(i);
+                item.setShiftingMode(false);
+                // set once again checked value, so view will be updated
+                item.setChecked(item.getItemData().isChecked());
+            }
+        } catch (NoSuchFieldException e) {
+            Log.e("ERROR NO SUCH FIELD", "Unable to get shift mode field");
+        } catch (IllegalAccessException e) {
+            Log.e("ERROR ILLEGAL ALG", "Unable to change value of shift mode");
+        }
+    }
+}

+ 58 - 0
app/src/main/java/com/itant/metrowc/tool/DonateTool.java

@@ -0,0 +1,58 @@
+package com.itant.metrowc.tool;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+
+import com.itant.metrowc.manager.ToastManager;
+
+import java.net.URLEncoder;
+
+/**
+ * Created by Jason on 2018/9/9.
+ */
+
+public class DonateTool {
+    public static boolean openAlipayPayPage(Activity context, String qrcode) {
+        try {
+            qrcode = URLEncoder.encode(qrcode, "utf-8");
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        try {
+            final String alipayqr = "alipayqr://platformapi/startapp?saId=10000007&clientVersion=3.7.0.0718&qrcode=" + qrcode;
+            openUri(context, alipayqr + "%3F_s%3Dweb-other&_t=" + System.currentTimeMillis());
+            return true;
+        } catch (Exception e) {
+            ToastManager.getInstance().toastShort("找不到浏览器");
+        }
+        return false;
+    }
+
+    public static void obtainRedPacket(Activity context, String url) {
+        Intent intent = new Intent();
+        intent.setAction("android.intent.action.VIEW");
+        Uri content_url = Uri.parse(url);
+        intent.setData(content_url);
+        try {
+            ActivityTool.startActivity(context, intent);
+        } catch (Exception e) {
+            ToastManager.getInstance().toastShort("找不到浏览器");
+        }
+    }
+
+    /**
+     * 发送一个intent
+     *
+     * @param context
+     * @param s
+     */
+    private static void openUri(Activity context, String s) {
+        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(s));
+        try {
+            ActivityTool.startActivity(context, intent);
+        } catch (Exception e) {
+            ToastManager.getInstance().toastShort("找不到浏览器");
+        }
+    }
+}

+ 49 - 0
app/src/main/java/com/itant/metrowc/tool/file/Assets.java

@@ -0,0 +1,49 @@
+package com.itant.metrowc.tool.file;
+
+
+import android.content.res.AssetManager;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class Assets {
+    private static final String TAG = "Assets";
+
+    public static void copyAssets(AssetManager assets, String assetPath, File destFolder)
+            throws IOException {
+        String[] childs = assets.list(assetPath);
+        if (childs.length == 0) {
+            copyFile(assets, assetPath, destFolder);
+        } else {
+            copyFolder(assets, assetPath, destFolder);
+        }
+    }
+
+    private static void copyFolder(AssetManager assets, String assetPath, File destFolder)
+            throws IOException {
+        Log.d(TAG, "copyFolder() called with: assets = [" + assets + "], assetPath = [" + assetPath + "], destFolder = [" + destFolder + "]");
+        String[] names = assets.list(assetPath);
+        for (String name : names) {
+            copyAssets(assets, assetPath + "/" + name, destFolder);
+        }
+    }
+
+    private static void copyFile(AssetManager assets, String assetPath, File destFolder)
+            throws IOException {
+        Log.d(TAG, "copyFile() called with: assets = [" + assets + "], assetPath = [" + assetPath + "], destFolder = [" + destFolder + "]");
+        File outFile = new File(destFolder, assetPath);
+        if (outFile.exists()) {
+            return;
+        }
+        outFile.getParentFile().mkdirs();
+        InputStream input = assets.open(assetPath);
+        OutputStream output = new FileOutputStream(outFile);
+        IOUtils.copy(input, output);
+        input.close();
+        output.close();
+    }
+}

+ 104 - 0
app/src/main/java/com/itant/metrowc/tool/file/FileTool.java

@@ -0,0 +1,104 @@
+package com.itant.metrowc.tool.file;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.channels.FileChannel;
+
+/**
+ * Created by Jason on 2018/9/16.
+ */
+
+public class FileTool {
+    public static void copyAssetToSDCard(Context context) {
+        // 把assets文件夹里db目录和里面的文件拷贝到内部存储里
+        try {
+            Assets.copyAssets(context.getAssets(), "db", getRootDir(context));
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    // 项目初始化的时候,先把所有的数据库复制到/data/data/包名/files/db
+    public static File getRootDir(Context context) {
+        return context.getFilesDir();
+    }
+
+    public static File getSkinDir(Context context) {
+        File dir = new File(getRootDir(context), "database");
+        return mkdirsIfNotExist(dir);
+    }
+
+    private static File mkdirsIfNotExist(File f) {
+        if (!f.exists()) {
+            f.mkdirs();
+        }
+        return f;
+    }
+
+    public static File createTempImageFile(Context context) {
+        return new File(context.getFilesDir(), "custom.jpg");
+    }
+
+    public static String getTempImagePath(Context context, Uri sourceTempUri, File destTempFile) {
+        String destTempUri = null;
+
+        FileInputStream inputStream = null;
+        FileChannel inputChannel = null;
+        FileOutputStream outputStream = null;
+        FileChannel outputChannel = null;
+        try {
+            inputStream = ((FileInputStream) context.getContentResolver().openInputStream(sourceTempUri));
+            if (inputStream == null) {
+                return null;
+            }
+            inputChannel = inputStream.getChannel();
+            if (inputChannel == null) {
+                return null;
+            }
+
+            outputStream = new FileOutputStream(destTempFile);
+            outputChannel = outputStream.getChannel();
+            outputChannel.transferFrom(inputChannel, 0, inputChannel.size());
+
+            destTempUri = destTempFile.getAbsolutePath();
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            if (inputStream != null) {
+                try {
+                    inputStream.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+            if (inputChannel != null) {
+                try {
+                    inputChannel.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+            if (outputStream != null) {
+                try {
+                    outputStream.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+            if (outputChannel != null) {
+                try {
+                    outputChannel.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+
+        return destTempUri;
+    }
+}

+ 360 - 0
app/src/main/java/com/itant/metrowc/tool/file/IOUtils.java

@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2018 Tran Le Duy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package com.itant.metrowc.tool.file;
+
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.nio.charset.Charset;
+import java.nio.charset.UnsupportedCharsetException;
+
+public class IOUtils {
+    /**
+     * Represents the end-of-file (or stream).
+     *
+     * @since 2.5 (made public)
+     */
+    public static final int EOF = -1;
+    /**
+     * The default buffer size ({@value}) to use for
+     * {@link #copyLarge(InputStream, OutputStream)}
+     * and
+     * {@link #copyLarge(Reader, Writer)}
+     */
+    private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
+    /**
+     * The default buffer size to use for the skip() methods.
+     */
+    private static final int SKIP_BUFFER_SIZE = 2048;
+    // read toString
+    //-----------------------------------------------------------------------
+
+    public static void copyNotIfExistAndClose(InputStream input, File outputFile) throws IOException {
+        if (outputFile.exists()) {
+            return;
+        }
+        outputFile.getParentFile().mkdirs();
+        FileOutputStream output = new FileOutputStream(outputFile);
+        copy(input, output);
+        output.close();
+        try {
+            input.close();
+        } catch (Exception ignored) {
+        }
+    }
+
+    public static String toString(File file) throws IOException {
+        FileInputStream input = new FileInputStream(file);
+        String s = toString(input);
+        input.close();
+        return s;
+    }
+
+    public static void writeAndClose(String content, File file) throws IOException {
+        FileOutputStream output = new FileOutputStream(file);
+        output.write(content.getBytes());
+        output.close();
+    }
+
+    /**
+     * Gets the contents of an <code>InputStream</code> as a String
+     * using the default character encoding of the platform.
+     * <p>
+     * This method buffers the input internally, so there is no need to use a
+     * <code>BufferedInputStream</code>.
+     *
+     * @param input the <code>InputStream</code> to read from
+     * @return the requested String
+     * @throws NullPointerException if the input is null
+     * @throws IOException          if an I/O error occurs
+     * @deprecated 2.5 use {@link #toString(InputStream, Charset)} instead
+     */
+    @Deprecated
+    public static String toString(final InputStream input) throws IOException {
+        return toString(input, Charset.forName("UTF-8"));
+    }
+
+    public static String toString(final InputStream input, final String encoding) throws IOException {
+        return toString(input, Charset.forName(encoding));
+    }
+
+    /**
+     * Gets the contents of an <code>InputStream</code> as a String
+     * using the specified character encoding.
+     * <p>
+     * This method buffers the input internally, so there is no need to use a
+     * <code>BufferedInputStream</code>.
+     * </p>
+     *
+     * @param input    the <code>InputStream</code> to read from
+     * @param encoding the encoding to use, null means platform default
+     * @return the requested String
+     * @throws NullPointerException if the input is null
+     * @throws IOException          if an I/O error occurs
+     * @since 2.3
+     */
+    public static String toString(final InputStream input, final Charset encoding) throws IOException {
+        final StringBuilderWriter sw = new StringBuilderWriter();
+        try {
+            copy(input, sw, encoding);
+            return sw.toString();
+        } finally {
+            sw.close();
+        }
+    }
+
+
+    /**
+     * Copies bytes from an <code>InputStream</code> to chars on a
+     * <code>Writer</code> using the specified character encoding.
+     * <p>
+     * This method buffers the input internally, so there is no need to use a
+     * <code>BufferedInputStream</code>.
+     * <p>
+     * This method uses {@link InputStreamReader}.
+     *
+     * @param input         the <code>InputStream</code> to read from
+     * @param output        the <code>Writer</code> to write to
+     * @param inputEncoding the encoding to use for the input stream, null means platform default
+     * @throws NullPointerException if the input or output is null
+     * @throws IOException          if an I/O error occurs
+     * @since 2.3
+     */
+    public static void copy(final InputStream input, final Writer output, final Charset inputEncoding)
+            throws IOException {
+        final InputStreamReader in = new InputStreamReader(input, inputEncoding);
+        copy(in, output);
+    }
+
+    /**
+     * Copies chars from a <code>Reader</code> to a <code>Writer</code>.
+     * <p>
+     * This method buffers the input internally, so there is no need to use a
+     * <code>BufferedReader</code>.
+     * <p>
+     * Large streams (over 2GB) will return a chars copied value of
+     * <code>-1</code> after the copy has completed since the correct
+     * number of chars cannot be returned as an int. For large streams
+     * use the <code>copyLarge(Reader, Writer)</code> method.
+     *
+     * @param input  the <code>Reader</code> to read from
+     * @param output the <code>Writer</code> to write to
+     * @return the number of characters copied, or -1 if &gt; Integer.MAX_VALUE
+     * @throws NullPointerException if the input or output is null
+     * @throws IOException          if an I/O error occurs
+     * @since 1.1
+     */
+    public static int copy(final Reader input, final Writer output) throws IOException {
+        final long count = copyLarge(input, output);
+        if (count > Integer.MAX_VALUE) {
+            return -1;
+        }
+        return (int) count;
+    }
+
+    /**
+     * Copies chars from a large (over 2GB) <code>Reader</code> to a <code>Writer</code>.
+     * <p>
+     * This method buffers the input internally, so there is no need to use a
+     * <code>BufferedReader</code>.
+     * <p>
+     * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}.
+     *
+     * @param input  the <code>Reader</code> to read from
+     * @param output the <code>Writer</code> to write to
+     * @return the number of characters copied
+     * @throws NullPointerException if the input or output is null
+     * @throws IOException          if an I/O error occurs
+     * @since 1.3
+     */
+    public static long copyLarge(final Reader input, final Writer output) throws IOException {
+        return copyLarge(input, output, new char[DEFAULT_BUFFER_SIZE]);
+    }
+
+    /**
+     * Copies chars from a large (over 2GB) <code>Reader</code> to a <code>Writer</code>.
+     * <p>
+     * This method uses the provided buffer, so there is no need to use a
+     * <code>BufferedReader</code>.
+     * <p>
+     *
+     * @param input  the <code>Reader</code> to read from
+     * @param output the <code>Writer</code> to write to
+     * @param buffer the buffer to be used for the copy
+     * @return the number of characters copied
+     * @throws NullPointerException if the input or output is null
+     * @throws IOException          if an I/O error occurs
+     * @since 2.2
+     */
+    public static long copyLarge(final Reader input, final Writer output, final char[] buffer) throws IOException {
+        long count = 0;
+        int n;
+        while (EOF != (n = input.read(buffer))) {
+            output.write(buffer, 0, n);
+            count += n;
+        }
+        return count;
+    }
+
+    /**
+     * Copy bytes from a large (over 2GB) <code>InputStream</code> to an
+     * <code>OutputStream</code>.
+     * <p>
+     * This method buffers the input internally, so there is no need to use a
+     * <code>BufferedInputStream</code>.
+     * <p>
+     * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}.
+     *
+     * @param input  the <code>InputStream</code> to read from
+     * @param output the <code>OutputStream</code> to write to
+     * @return the number of bytes copied
+     * @throws NullPointerException if the input or output is null
+     * @throws IOException          if an I/O error occurs
+     * @since 1.3
+     */
+    public static long copyLarge(InputStream input, OutputStream output)
+            throws IOException {
+        return copyLarge(input, output, new byte[DEFAULT_BUFFER_SIZE]);
+    }
+
+    /**
+     * Copy bytes from a large (over 2GB) <code>InputStream</code> to an
+     * <code>OutputStream</code>.
+     * <p>
+     * This method uses the provided buffer, so there is no need to use a
+     * <code>BufferedInputStream</code>.
+     * <p>
+     *
+     * @param input  the <code>InputStream</code> to read from
+     * @param output the <code>OutputStream</code> to write to
+     * @param buffer the buffer to use for the copy
+     * @return the number of bytes copied
+     * @throws NullPointerException if the input or output is null
+     * @throws IOException          if an I/O error occurs
+     * @since 2.2
+     */
+    public static long copyLarge(InputStream input, OutputStream output, byte[] buffer)
+            throws IOException {
+        long count = 0;
+        int n = 0;
+        while (EOF != (n = input.read(buffer))) {
+            output.write(buffer, 0, n);
+            count += n;
+        }
+        return count;
+    }
+
+    // copy from InputStream
+    //-----------------------------------------------------------------------
+
+    /**
+     * Copy bytes from an <code>InputStream</code> to an
+     * <code>OutputStream</code>.
+     * <p>
+     * This method buffers the input internally, so there is no need to use a
+     * <code>BufferedInputStream</code>.
+     * <p>
+     * Large streams (over 2GB) will return a bytes copied value of
+     * <code>-1</code> after the copy has completed since the correct
+     * number of bytes cannot be returned as an int. For large streams
+     * use the <code>copyLarge(InputStream, OutputStream)</code> method.
+     *
+     * @param input  the <code>InputStream</code> to read from
+     * @param output the <code>OutputStream</code> to write to
+     * @return the number of bytes copied, or -1 if &gt; Integer.MAX_VALUE
+     * @throws NullPointerException if the input or output is null
+     * @throws IOException          if an I/O error occurs
+     * @since 1.1
+     */
+    public static int copy(InputStream input, OutputStream output) throws IOException {
+        long count = copyLarge(input, output);
+        if (count > Integer.MAX_VALUE) {
+            return -1;
+        }
+        return (int) count;
+    }
+
+
+    /**
+     * Writes chars from a <code>String</code> to bytes on an
+     * <code>OutputStream</code> using the default character encoding of the
+     * platform.
+     * <p>
+     * This method uses {@link String#getBytes()}.
+     *
+     * @param data   the <code>String</code> to write, null ignored
+     * @param output the <code>OutputStream</code> to write to
+     * @throws NullPointerException if output is null
+     * @throws IOException          if an I/O error occurs
+     * @since 1.1
+     */
+    public static void write(String data, OutputStream output)
+            throws IOException {
+        write(data, output, Charset.forName("UTF-8"));
+    }
+
+    /**
+     * Writes chars from a <code>String</code> to bytes on an
+     * <code>OutputStream</code> using the specified character encoding.
+     * <p>
+     * This method uses {@link String#getBytes(String)}.
+     *
+     * @param data     the <code>String</code> to write, null ignored
+     * @param output   the <code>OutputStream</code> to write to
+     * @param encoding the encoding to use, null means platform default
+     * @throws NullPointerException if output is null
+     * @throws IOException          if an I/O error occurs
+     * @since 2.3
+     */
+    public static void write(String data, OutputStream output, Charset encoding) throws IOException {
+        if (data != null) {
+            output.write(data.getBytes(encoding));
+        }
+    }
+
+    /**
+     * Writes chars from a <code>String</code> to bytes on an
+     * <code>OutputStream</code> using the specified character encoding.
+     * <p>
+     * Character encoding names can be found at
+     * <a href="http://www.iana.org/assignments/character-sets">IANA</a>.
+     * <p>
+     * This method uses {@link String#getBytes(String)}.
+     *
+     * @param data     the <code>String</code> to write, null ignored
+     * @param output   the <code>OutputStream</code> to write to
+     * @param encoding the encoding to use, null means platform default
+     * @throws NullPointerException        if output is null
+     * @throws IOException                 if an I/O error occurs
+     * @throws UnsupportedCharsetException thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not
+     *                                     supported.
+     * @since 1.1
+     */
+    public static void write(String data, OutputStream output, String encoding)
+            throws IOException {
+        write(data, output, Charset.forName(encoding));
+    }
+}

+ 164 - 0
app/src/main/java/com/itant/metrowc/tool/file/StringBuilderWriter.java

@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2018 Tran Le Duy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+package com.itant.metrowc.tool.file;
+
+import java.io.Serializable;
+import java.io.Writer;
+
+/**
+ * {@link Writer} implementation that outputs to a {@link StringBuilder}.
+ * <p>
+ * <strong>NOTE:</strong> This implementation, as an alternative to
+ * <code>java.io.StringWriter</code>, provides an <i>un-synchronized</i>
+ * (i.e. for use in a single thread) implementation for better performance.
+ * For safe usage with multiple {@link Thread}s then
+ * <code>java.io.StringWriter</code> should be used.
+ *
+ * @since 2.0
+ */
+public class StringBuilderWriter extends Writer implements Serializable {
+
+    private static final long serialVersionUID = -146927496096066153L;
+    private final StringBuilder builder;
+
+    /**
+     * Constructs a new {@link StringBuilder} instance with default capacity.
+     */
+    public StringBuilderWriter() {
+        this.builder = new StringBuilder();
+    }
+
+    /**
+     * Constructs a new {@link StringBuilder} instance with the specified capacity.
+     *
+     * @param capacity The initial capacity of the underlying {@link StringBuilder}
+     */
+    public StringBuilderWriter(final int capacity) {
+        this.builder = new StringBuilder(capacity);
+    }
+
+    /**
+     * Constructs a new instance with the specified {@link StringBuilder}.
+     *
+     * <p>If {@code builder} is null a new instance with default capacity will be created.</p>
+     *
+     * @param builder The String builder. May be null.
+     */
+    public StringBuilderWriter(final StringBuilder builder) {
+        this.builder = builder != null ? builder : new StringBuilder();
+    }
+
+    /**
+     * Appends a single character to this Writer.
+     *
+     * @param value The character to append
+     * @return This writer instance
+     */
+    @Override
+    public Writer append(final char value) {
+        builder.append(value);
+        return this;
+    }
+
+    /**
+     * Appends a character sequence to this Writer.
+     *
+     * @param value The character to append
+     * @return This writer instance
+     */
+    @Override
+    public Writer append(final CharSequence value) {
+        builder.append(value);
+        return this;
+    }
+
+    /**
+     * Appends a portion of a character sequence to the {@link StringBuilder}.
+     *
+     * @param value The character to append
+     * @param start The index of the first character
+     * @param end The index of the last character + 1
+     * @return This writer instance
+     */
+    @Override
+    public Writer append(final CharSequence value, final int start, final int end) {
+        builder.append(value, start, end);
+        return this;
+    }
+
+    /**
+     * Closing this writer has no effect.
+     */
+    @Override
+    public void close() {
+        // no-op
+    }
+
+    /**
+     * Flushing this writer has no effect.
+     */
+    @Override
+    public void flush() {
+        // no-op
+    }
+
+
+    /**
+     * Writes a String to the {@link StringBuilder}.
+     *
+     * @param value The value to write
+     */
+    @Override
+    public void write(final String value) {
+        if (value != null) {
+            builder.append(value);
+        }
+    }
+
+    /**
+     * Writes a portion of a character array to the {@link StringBuilder}.
+     *
+     * @param value The value to write
+     * @param offset The index of the first character
+     * @param length The number of characters to write
+     */
+    @Override
+    public void write(final char[] value, final int offset, final int length) {
+        if (value != null) {
+            builder.append(value, offset, length);
+        }
+    }
+
+    /**
+     * Returns the underlying builder.
+     *
+     * @return The underlying builder
+     */
+    public StringBuilder getBuilder() {
+        return builder;
+    }
+
+    /**
+     * Returns {@link StringBuilder#toString()}.
+     *
+     * @return The contents of the String builder.
+     */
+    @Override
+    public String toString() {
+        return builder.toString();
+    }
+}

+ 140 - 0
app/src/main/java/com/itant/metrowc/widget/AlignTextView.java

@@ -0,0 +1,140 @@
+package com.itant.metrowc.widget;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.support.v7.widget.AppCompatTextView;
+import android.text.Layout;
+import android.text.TextPaint;
+import android.util.AttributeSet;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 两端对齐的textview,可以设置最后一行靠左,靠右,居中对齐
+ *
+ * @author yuyh.
+ * @date 16/4/10.
+ */
+public class AlignTextView extends AppCompatTextView {
+    private float textHeight; // 单行文字高度
+    private int width; // textview宽度
+    private List<String> lines = new ArrayList<String>(); // 分割后的行
+    private List<Integer> tailLines = new ArrayList<Integer>(); // 尾行
+    private Align align = Align.ALIGN_LEFT; // 默认最后一行左对齐
+
+    // 尾行对齐方式,针对段落最后一行
+    public enum Align {
+        ALIGN_LEFT,
+        ALIGN_CENTER,
+        ALIGN_RIGHT,
+    }
+
+    public AlignTextView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        TextPaint paint = getPaint();
+        paint.setColor(getCurrentTextColor());
+        paint.drawableState = getDrawableState();
+        width = getMeasuredWidth();
+        String text = getText().toString();
+
+        Paint.FontMetrics fm = paint.getFontMetrics();
+        // 计算行高
+        Layout layout = getLayout();
+
+        // layout.getLayout()在4.4.3出现NullPointerException
+        if (layout == null) {
+            return;
+        }
+
+        textHeight = fm.descent - fm.ascent;
+        textHeight = textHeight * layout.getSpacingMultiplier() + layout.getSpacingAdd();
+
+        float firstHeight = getTextSize();
+
+        int gravity = getGravity();
+        if ((gravity & 0x1000) == 0) { // 是否垂直居中
+            firstHeight = firstHeight + (textHeight - firstHeight) / 2;
+        }
+
+        int paddingLeft = getPaddingLeft();
+        int paddingRight = getPaddingRight();
+        width = width - paddingLeft - paddingRight;
+
+        lines.clear();
+        tailLines.clear();
+
+        // 文本含有换行符时,分割单独处理
+        String[] items = text.split("\\n");
+        for (String item : items) {
+            calc(paint, item);
+        }
+
+        for (int i = 0; i < lines.size(); i++) {
+            float drawY = i * textHeight + firstHeight;
+            String line = lines.get(i);
+            // 绘画起始x坐标
+            float drawSpacingX = paddingLeft;
+            float gap = (width - paint.measureText(line));
+            float interval = gap / (line.length() - 1);
+
+            // 绘制最后一行
+            if (tailLines.contains(i)) {
+                interval = 0;
+                if (align == Align.ALIGN_CENTER)
+                    drawSpacingX += gap / 2;
+                else if (align == Align.ALIGN_RIGHT)
+                    drawSpacingX += gap;
+            }
+
+            for (int j = 0; j < line.length(); j++) {
+                float drawX = paint.measureText(line.substring(0, j))
+                        + interval * j;
+                canvas.drawText(line.substring(j, j + 1), drawX + drawSpacingX,
+                        drawY, paint);
+            }
+        }
+    }
+
+    /**
+     * 设置尾行对齐方式
+     *
+     * @param align
+     */
+    public void setAlign(Align align) {
+        this.align = align;
+        invalidate();
+    }
+
+    /**
+     * 计算每行应显示的文本数
+     *
+     * @param text
+     * @return
+     */
+    private void calc(Paint paint, String text) {
+        if (text.length() == 0) {
+            lines.add("\n");
+            return;
+        }
+        StringBuffer sb = new StringBuffer("");
+        int startPosition = 0; // 起始位置
+        for (int i = 0; i < text.length(); i++) {
+            if (paint.measureText(text.substring(startPosition, i + 1)) > width) {
+                startPosition = i;
+                lines.add(sb.toString());
+                sb = new StringBuffer();
+            }
+            sb.append(text.charAt(i));
+        }
+        if (sb.length() > 0)
+            lines.add(sb.toString());
+
+        tailLines.add(lines.size() - 1);
+    }
+}

+ 44 - 0
app/src/main/java/com/itant/metrowc/widget/ScrollableViewPager.java

@@ -0,0 +1,44 @@
+package com.itant.metrowc.widget;
+
+import android.content.Context;
+import android.support.v4.view.ViewPager;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+public class ScrollableViewPager extends ViewPager {
+
+    private boolean scrollable = false;
+
+
+    public ScrollableViewPager(Context context) {
+        super(context);
+    }
+
+    public ScrollableViewPager(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        if (!scrollable) {
+            return false;
+        }
+        return super.onTouchEvent(ev);
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        if (!scrollable) {
+            return false;
+        }
+        return super.onInterceptTouchEvent(ev);
+    }
+
+    public boolean isCanScrollble() {
+        return scrollable;
+    }
+
+    public void setCanScrollble(boolean scrollable) {
+        this.scrollable = scrollable;
+    }
+}  

+ 5 - 0
app/src/main/res/anim/anim_slide_left_in.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+	<translate android:fromXDelta="-100%" android:toXDelta="0.0%"
+		android:duration="300" />
+</set>

+ 5 - 0
app/src/main/res/anim/anim_slide_left_out.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+	<translate android:fromXDelta="0.0%" android:toXDelta="-100%"
+		android:duration="300" />
+</set>

+ 9 - 0
app/src/main/res/anim/anim_slide_right_in.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <translate
+        android:duration="300"
+        android:fromXDelta="100.0%"
+        android:interpolator="@android:anim/accelerate_interpolator"
+        android:toXDelta="0.0%" />
+</set>

+ 9 - 0
app/src/main/res/anim/anim_slide_right_out.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <translate
+        android:duration="300"
+        android:fromXDelta="0.0%"
+        android:interpolator="@android:anim/accelerate_interpolator"
+        android:toXDelta="100.0%" />
+</set>

+ 9 - 0
app/src/main/res/drawable/ic_forward.xml

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="1000.0"
+        android:viewportHeight="1000.0">
+    <path
+        android:fillColor="#616161"
+        android:pathData="M278.1,10l-37,55.5l443.8,443.8L241.1,953l37,37l480.7-480.8L278.1,10L278.1,10z"/>
+</vector>

+ 18 - 0
app/src/main/res/drawable/selector_white_pressed.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:state_pressed="true">
+        <shape android:shape="rectangle">
+            <solid android:color="@color/gray_divider_light" />
+            <!--<stroke android:color="@color/white" android:width="@dimen/height_divider"/>-->
+        </shape>
+    </item>
+
+    <item>
+        <shape android:shape="rectangle">
+            <!--不点击时的颜色-->
+            <solid android:color="@color/white" />
+            <!--<stroke android:color="@color/white" android:width="@dimen/height_divider"/>-->
+        </shape>
+    </item>
+</selector>

+ 6 - 0
app/src/main/res/drawable/shape_round_white_solid.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="@color/white" />
+    <corners android:radius="@dimen/size_image_ss"/>
+</shape>

+ 9 - 0
app/src/main/res/drawable/tab_about_24dp.xml

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M12 4C9.79 4 8 5.79 8 8s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm0 10c-6.102 0-8 4-8 4v2h16v-2s-1.898-4-8-4z"/>
+</vector>

+ 9 - 0
app/src/main/res/drawable/tab_map_24dp.xml

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z" />
+</vector>

+ 13 - 0
app/src/main/res/drawable/tab_route_24dp.xml

@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="1024.0"
+    android:viewportWidth="1024.0">
+    <!--<path
+        android:fillColor="#FF000000"
+        android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
+-->
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M719.2 480H305.6c-57.6 0-108-41.6-114.4-99.2-7.2-67.2 45.6-124.8 112-124.8H728v64l166.4-96L728 128v64H303.2c-97.6 0-176 78.4-176 176s78.4 176 176 176h413.6c57.6 0 108 41.6 114.4 99.2 8 67.2-45.6 124.8-111.2 124.8H296v-64l-168 98.4L296 896v-64h423.2c97.6 0 176-78.4 176-176s-78.4-176-176-176z" />
+</vector>

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

@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/white"
+    android:fitsSystemWindows="true">
+
+
+    <com.itant.metrowc.widget.ScrollableViewPager
+        android:id="@+id/vp_main"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        android:paddingBottom="@dimen/design_bottom_navigation_height_with_divider"/>
+
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="0.5dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        android:layout_marginBottom="@dimen/design_bottom_navigation_height"
+        android:background="@color/gray_divider"/>
+
+    <android.support.design.widget.BottomNavigationView
+        android:id="@+id/navigation"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="0dp"
+        android:layout_marginStart="0dp"
+        android:background="@color/white_slight"
+        app:elevation="0dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:menu="@menu/navigation" />
+
+</android.support.constraint.ConstraintLayout>

+ 221 - 0
app/src/main/res/layout/fragment_about.xml

@@ -0,0 +1,221 @@
+<?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:scrollbarSize="@dimen/size_zero"
+    android:scrollbars="none"
+    android:background="@color/white">
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        xmlns:skin="http://schemas.android.com/android/skin">
+
+        <RelativeLayout
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/height_cover"
+            android:background="@color/colorPrimary"
+            skin:enable="true">
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_centerVertical="true"
+                android:gravity="center_horizontal"
+                android:orientation="vertical">
+
+                <FrameLayout
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:background="@drawable/shape_round_white_solid"
+                    android:padding="@dimen/margin_s">
+                    <ImageView
+                        android:id="@+id/iv_head"
+                        android:layout_width="@dimen/size_image_big"
+                        android:layout_height="@dimen/size_image_big"
+                        android:scaleType="fitXY"
+                        android:src="@mipmap/wc" />
+                </FrameLayout>
+
+
+                <TextView
+                    android:id="@+id/tv_app_name"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/margin_s"
+                    android:text="@string/app_name"
+                    android:textColor="@color/white"
+                    android:textSize="@dimen/text_sub_title" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/margin_ss"
+                    android:text="@string/about_content"
+                    android:textColor="@color/white"
+                    android:textSize="@dimen/text_s" />
+            </LinearLayout>
+        </RelativeLayout>
+
+
+        <android.support.v7.widget.CardView
+            xmlns:card_view="http://schemas.android.com/apk/res-auto"
+            android:id="@+id/cv_article_item"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:background="@color/white_slight"
+            android:clickable="true"
+            android:focusable="true"
+            android:foreground="?android:attr/selectableItemBackground"
+            card_view:cardCornerRadius="@dimen/height_indicator"
+            card_view:cardElevation="@dimen/height_indicator"
+            android:layout_marginLeft="@dimen/margin_s"
+            android:layout_marginRight="@dimen/margin_s"
+            android:layout_marginTop="@dimen/margin_default"
+            android:layout_marginBottom="@dimen/margin_default">
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:padding="@dimen/margin_s">
+                <com.itant.metrowc.widget.AlignTextView
+                    android:id="@+id/tv_desc"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:textColor="@color/gray_text"
+                    android:textIsSelectable="false"
+                    android:textSize="@dimen/text_normal"
+                    android:textColorHighlight="@color/colorPrimary"/>
+            </FrameLayout>
+        </android.support.v7.widget.CardView>
+
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/height_divider_micro"
+            android:background="@color/gray_divider_light" />
+        <!--子项菜单-->
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            android:background="@color/white_slight">
+
+            <LinearLayout
+                android:id="@+id/ll_vip"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:background="@drawable/selector_white_pressed"
+                android:gravity="center_vertical"
+                android:minHeight="@dimen/height_tab_bar"
+                android:orientation="horizontal"
+                android:paddingLeft="@dimen/margin_default"
+                android:paddingRight="@dimen/margin_default">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/about_sync"
+                    android:textColor="@color/gray_text"
+                    android:textSize="@dimen/text_normal_p" />
+
+                <Space
+                    android:layout_width="@dimen/size_zero"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1" />
+
+                <android.support.v7.widget.AppCompatImageView
+                    android:layout_width="@dimen/size_image_ss"
+                    android:layout_height="@dimen/size_image_ss"
+                    android:src="@drawable/ic_forward"
+                    android:tint="@color/gray_divider" />
+
+            </LinearLayout>
+
+            <View
+                android:layout_width="match_parent"
+                android:layout_height="@dimen/height_divider_micro"
+                android:layout_marginLeft="@dimen/margin_default"
+                android:background="@color/gray_divider_light" />
+
+            <LinearLayout
+                android:id="@+id/ll_contact"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:background="@drawable/selector_white_pressed"
+                android:gravity="center_vertical"
+                android:minHeight="@dimen/height_tab_bar"
+                android:orientation="horizontal"
+                android:paddingLeft="@dimen/margin_default"
+                android:paddingRight="@dimen/margin_default">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/about_feedback"
+                    android:textColor="@color/gray_text"
+                    android:textSize="@dimen/text_normal_p" />
+
+                <Space
+                    android:layout_width="@dimen/size_zero"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1" />
+
+                <android.support.v7.widget.AppCompatImageView
+                    android:layout_width="@dimen/size_image_ss"
+                    android:layout_height="@dimen/size_image_ss"
+                    android:src="@drawable/ic_forward"
+                    android:tint="@color/gray_divider" />
+
+            </LinearLayout>
+
+            <View
+                android:layout_width="match_parent"
+                android:layout_height="@dimen/height_divider_micro"
+                android:background="@color/gray_divider_light"
+                android:layout_marginLeft="@dimen/margin_default"/>
+
+            <LinearLayout
+                android:id="@+id/ll_donate"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:background="@drawable/selector_white_pressed"
+                android:gravity="center_vertical"
+                android:minHeight="@dimen/height_tab_bar"
+                android:orientation="horizontal"
+                android:paddingLeft="@dimen/margin_default"
+                android:paddingRight="@dimen/margin_default">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="赞助支持"
+                    android:textColor="@color/gray_text"
+                    android:textSize="@dimen/text_normal_p" />
+
+                <Space
+                    android:layout_width="@dimen/size_zero"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:textSize="@dimen/text_s"
+                    android:textColor="@color/gray_text_hint"
+                    android:text="建议1元"
+                    android:layout_marginRight="@dimen/margin_s"/>
+
+                <android.support.v7.widget.AppCompatImageView
+                    android:layout_width="@dimen/size_image_ss"
+                    android:layout_height="@dimen/size_image_ss"
+                    android:src="@drawable/ic_forward"
+                    android:tint="@color/gray_divider" />
+
+            </LinearLayout>
+            <View
+                android:layout_width="match_parent"
+                android:layout_height="@dimen/height_divider_micro"
+                android:background="@color/gray_divider_light" />
+        </LinearLayout>
+    </LinearLayout>
+</ScrollView>

+ 22 - 0
app/src/main/res/layout/fragment_map.xml

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout 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="match_parent"
+    android:background="@color/white">
+    <WebView
+        android:id="@+id/wv_map"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"/>
+
+    <Switch
+        android:id="@+id/switch_show_wc"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/switch_wc"
+        android:layout_gravity="right"
+        android:textColor="@color/colorPrimaryDark"
+        android:textSize="@dimen/size_sp_small"
+        android:layout_margin="@dimen/size_dp_tiny"
+        android:textStyle="bold"/>
+</FrameLayout>

+ 12 - 0
app/src/main/res/layout/fragment_route.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout 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="match_parent"
+    android:background="@color/white">
+
+    <ListView
+        android:id="@+id/lv_line"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"/>
+</FrameLayout>

+ 42 - 0
app/src/main/res/layout/item_line.xml

@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:id="@+id/ll_line_item">
+    <LinearLayout
+        android:id="@+id/ll_line_bg"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="@drawable/selector_white_pressed"
+        android:gravity="center_vertical"
+        android:minHeight="@dimen/height_tab_bar"
+        android:orientation="horizontal"
+        android:paddingLeft="@dimen/margin_default"
+        android:paddingRight="@dimen/margin_default">
+
+        <TextView
+            android:id="@+id/tv_line_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textColor="@color/white"
+            android:textSize="@dimen/text_normal_p" />
+
+        <Space
+            android:layout_width="@dimen/size_zero"
+            android:layout_height="wrap_content"
+            android:layout_weight="1" />
+
+        <TextView
+            android:id="@+id/tv_description"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="@dimen/text_s"
+            android:textColor="@color/white"/>
+
+    </LinearLayout>
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/height_divider_micro"
+        android:background="@color/gray_divider_light" />
+</LinearLayout>

+ 42 - 0
app/src/main/res/layout/item_station.xml

@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:id="@+id/ll_line_item">
+    <LinearLayout
+        android:id="@+id/ll_line_bg"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="@drawable/selector_white_pressed"
+        android:gravity="center_vertical"
+        android:minHeight="@dimen/height_tab_bar"
+        android:orientation="horizontal"
+        android:paddingLeft="@dimen/margin_default"
+        android:paddingRight="@dimen/margin_default">
+
+        <TextView
+            android:id="@+id/tv_line_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textColor="@color/black"
+            android:textSize="@dimen/text_normal_p" />
+
+        <Space
+            android:layout_width="@dimen/size_zero"
+            android:layout_height="wrap_content"
+            android:layout_weight="1" />
+
+        <TextView
+            android:id="@+id/tv_description"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="@dimen/text_s"
+            android:textColor="@color/white"/>
+
+    </LinearLayout>
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/height_divider_micro"
+        android:background="@color/gray_divider_light" />
+</LinearLayout>

+ 19 - 0
app/src/main/res/menu/navigation.xml

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item
+        android:id="@+id/navigation_article"
+        android:icon="@drawable/tab_map_24dp"
+        android:title="@string/menu_map" />
+
+    <item
+        android:id="@+id/navigation_photo"
+        android:icon="@drawable/tab_route_24dp"
+        android:title="@string/menu_route" />
+
+    <item
+        android:id="@+id/navigation_me"
+        android:icon="@drawable/tab_about_24dp"
+        android:title="@string/menu_about" />
+
+</menu>

BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-xxhdpi/wc.png


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

@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="colorPrimary">#1b82d1</color>
+    <color name="colorPrimaryDark">#1b82d1</color>
+    <color name="colorAccent">#1b82d1</color>
+
+
+    <color name="blue_1">#3f92ff</color>
+    <color name="blue_2">#8ab6ef</color>
+    <color name="blue_3">#8ab6ef</color>
+    <color name="blue_4">#c1e7fe</color>
+    <color name="blue_input_bg">#80d5ff</color>
+    <color name="blue_5">#c2e8ff</color>
+    <color name="green_1">#1abc9c</color>
+    <color name="green_2">#27ae60</color>
+    <color name="green_3">#4ade87</color>
+    <color name="yellow_1">#f0ad4e</color>
+    <color name="yellow_2">#f1c40f</color>
+    <color name="yellow_3">#f4cd84</color>
+    <color name="yellow_4">#feeda2</color>
+    <!--<color name="red_1">#d9534f</color>
+    <color name="red_2">#ff8573</color>
+    <color name="red_3">#f68e8e</color>-->
+    <color name="gray_1">#bac4c5</color>
+    <color name="gray_text">#808080</color>
+    <color name="gray_divider">#dcdcdc</color>
+    <color name="gray_bg">#f1f1f1</color>
+    <color name="purple_1">#8e44ad</color>
+    <color name="purple_2">#c28eff</color>
+    <color name="black_blue">#34495e</color>
+    <color name="white">#FFFFFF</color>
+    <color name="white_red">#FFFFF2F4</color>
+    <color name="white_slight">#f7f8f8</color>
+    <color name="black">#FF000000</color>
+    <color name="black_text">#212121</color>
+
+    <color name="white_transparent">#26000000</color>
+    <color name="white_transparent_p">#80FFFFFF</color>
+    <color name="transparent">#00000000</color>
+
+    <color name="gray_disabled">#4DFFFFFF</color>
+    <!-- 内容文字,正文 -->
+    <color name="gray_text_s">#777777</color>
+    <!-- 辅助文字,提示 -->
+    <color name="gray_text_hint">#ababab</color>
+    <!--<color name="gray_light">#CCCCCC</color>-->
+    <!-- 分割线 -->
+    <color name="gray_divider_light">#d9d9d9</color>
+    <color name="gray_divider_light_s">#ebebeb</color>
+    <color name="gray_divider_light_ss">#eeeeee</color>
+    <!--<color name="gray_divider_light_ss">#f0f0f0</color>-->
+    <color name="gray_loading">#26E0E0E0</color>
+    <color name="gray_tab_unchecked">#717171</color>
+</resources>

+ 100 - 0
app/src/main/res/values/dimens.xml

@@ -0,0 +1,100 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <!-- Default screen margins, per the Android Design guidelines. -->
+    <dimen name="activity_horizontal_margin">10dp</dimen>
+    <dimen name="activity_vertical_margin">10dp</dimen>
+
+    <!--BottomNavigationView 的选中没有选中的字体大小-->
+    <dimen name="design_bottom_navigation_active_text_size" tools:override="true">10.5sp</dimen>
+    <dimen name="design_bottom_navigation_text_size" tools:override="true">9.5sp</dimen>
+    <dimen name="design_bottom_navigation_height" tools:override="true">50dp</dimen>
+    <dimen name="design_bottom_navigation_height_with_divider" tools:override="true">50.5dp</dimen>
+
+    <!--自定义-->
+    <dimen name="size_sp_gigantic">24sp</dimen>
+    <dimen name="size_sp_enormous">22sp</dimen>
+    <dimen name="size_sp_huge">20sp</dimen>
+    <dimen name="size_sp_large">18sp</dimen>
+    <dimen name="size_sp_big">16sp</dimen>
+    <dimen name="size_sp_normal">14sp</dimen>
+    <dimen name="size_sp_small">12sp</dimen>
+    <dimen name="size_sp_mini">10sp</dimen>
+    <dimen name="size_sp_tiny">8sp</dimen>
+    <dimen name="size_sp_micro">6sp</dimen>
+
+    <dimen name="size_dp_gigantic">24dp</dimen>
+    <dimen name="size_dp_enormous">22dp</dimen>
+    <dimen name="size_dp_huge">20dp</dimen>
+    <dimen name="size_dp_large">18dp</dimen>
+    <dimen name="size_dp_big">16dp</dimen>
+    <dimen name="size_dp_normal">14dp</dimen>
+    <dimen name="size_dp_small">12dp</dimen>
+    <dimen name="size_dp_mini">10dp</dimen>
+    <dimen name="size_dp_tiny">8dp</dimen>
+    <dimen name="size_dp_micro">6dp</dimen>
+    <dimen name="size_small_space">2dp</dimen>
+    <dimen name="size_normal_space">4dp</dimen>
+
+    <dimen name="size_btn_height">64dp</dimen>
+
+    <dimen name="padding_horizon">10dp</dimen>
+    <dimen name="padding_vertical">8dp</dimen>
+
+    <!--右侧菜单-->
+    <dimen name="tool_bar_text_size">22sp</dimen>
+    <dimen name="main_screen_side_padding">10dp</dimen>
+    <dimen name="text_top_padding">25dp</dimen>
+    <dimen name="title_text_size">26sp</dimen>
+    <dimen name="tool_bar_height">56dp</dimen>
+    <dimen name="size_item_height">42dp</dimen>
+    <dimen name="size_divider_height">0.3dp</dimen>
+    <dimen name="height_more_image">25dp</dimen>
+    <dimen name="height_more">12dp</dimen>
+    <dimen name="height_top_bar">48dp</dimen>
+
+    <dimen name="size_me_menu">32dp</dimen>
+
+    <dimen name="size_zero">0dp</dimen>
+
+    <dimen name="margin_default">16dp</dimen>
+    <dimen name="margin_message">12dp</dimen>
+    <dimen name="margin_s">8dp</dimen>
+    <dimen name="margin_ss">4dp</dimen>
+    <dimen name="margin_sss">2dp</dimen>
+    <dimen name="size_image_bar">120dp</dimen>
+    <dimen name="size_image_huge">96dp</dimen>
+    <dimen name="size_image_list">144dp</dimen>
+    <dimen name="size_image_big">72dp</dimen>
+    <dimen name="size_image_normal">48dp</dimen>
+    <dimen name="size_image_s">24dp</dimen>
+    <dimen name="size_image_setting">20dp</dimen>
+    <dimen name="size_image_ss">16dp</dimen>
+    <dimen name="size_image_sss">8dp</dimen>
+
+    <dimen name="text_title">17sp</dimen>
+    <dimen name="text_sub_title">16sp</dimen>
+    <dimen name="text_normal_p">15sp</dimen>
+    <dimen name="text_normal">14sp</dimen>
+    <dimen name="text_normal_s">13sp</dimen>
+    <dimen name="text_s">12sp</dimen>
+    <dimen name="text_ss">10sp</dimen>
+    <dimen name="text_sss">8sp</dimen>
+
+    <dimen name="radius_default">4dp</dimen>
+    <dimen name="radius_indicator">1.5dp</dimen>
+    <dimen name="radius_s">2.5dp</dimen>
+
+    <dimen name="width_min_code">64dp</dimen>
+    <dimen name="height_tab_bar">48dp</dimen>
+    <dimen name="height_message_item">64dp</dimen>
+    <dimen name="height_indicator">3dp</dimen>
+    <dimen name="height_divider">0.8dp</dimen>
+    <dimen name="height_divider_micro">1px</dimen>
+    <dimen name="height_edit_text">40dp</dimen>
+    <dimen name="height_sliding_tab">42dp</dimen>
+    <dimen name="height_edit_text_s">32dp</dimen>
+    <dimen name="height_edit_text_search">36dp</dimen>
+    <dimen name="height_cover">192dp</dimen>
+
+    <dimen name="width_indicator">25dp</dimen>
+
+</resources>

Plik diff jest za duży
+ 24 - 0
app/src/main/res/values/strings.xml


+ 26 - 0
app/src/main/res/values/styles.xml

@@ -0,0 +1,26 @@
+<resources>
+
+    <!-- Base application theme. -->
+    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
+        <!-- Customize your theme here. -->
+        <item name="colorPrimary">@color/colorPrimary</item>
+        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+        <item name="colorAccent">@color/colorAccent</item>
+    </style>
+
+    <style name="AppTheme.PhotoDetailTheme">
+        <item name="colorPrimary">#000000</item>
+        <item name="colorPrimaryDark">#000000</item>
+        <!--解决滑动时不透明的bug-->
+        <item name="android:windowBackground">@android:color/transparent</item>
+        <!--<item name="android:windowDisablePreview">true</item>-->
+        <item name="android:windowIsTranslucent">true</item>
+        <item name="android:windowNoTitle">true</item>
+    </style>
+
+    <style name="AppTheme.Secure">
+        <!--<item name="android:windowIsTranslucent">true</item>-->
+        <!--用下面这个属性代替windowIsTranslucent,否则在8.0上配合portrait属性会导致闪退-->
+        <item name="android:windowDisablePreview">true</item>
+    </style>
+</resources>

+ 17 - 0
app/src/test/java/com/itant/metrowc/ExampleUnitTest.java

@@ -0,0 +1,17 @@
+package com.itant.metrowc;
+
+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);
+    }
+}

+ 27 - 0
build.gradle

@@ -0,0 +1,27 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+    repositories {
+        google()
+        jcenter()
+        
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:3.5.0'
+        
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        jcenter()
+        
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}

+ 15 - 0
gradle.properties

@@ -0,0 +1,15 @@
+# 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=-Xmx1536m
+# 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
+
+

BIN
gradle/wrapper/gradle-wrapper.jar


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

@@ -0,0 +1,6 @@
+#Tue Oct 29 11:13:52 CST 2019
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip

+ 172 - 0
gradlew

@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  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=""
+
+# 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, switch paths to Windows format before running java
+if $cygwin ; 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=$((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"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"

+ 84 - 0
gradlew.bat

@@ -0,0 +1,84 @@
+@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 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=
+
+@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 init
+
+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 init
+
+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
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+: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 %CMD_LINE_ARGS%
+
+: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

+ 2 - 0
settings.gradle

@@ -0,0 +1,2 @@
+include ':app'
+rootProject.name='MetroWC'