();
102 |
103 | for (String perm : permissions) {
104 | if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(activity, perm)) {
105 | toApplyList.add(perm);
106 | //进入到这里代表没有权限.
107 |
108 | }
109 | }
110 | String tmpList[] = new String[toApplyList.size()];
111 | if (!toApplyList.isEmpty()) {
112 | ActivityCompat.requestPermissions(activity, toApplyList.toArray(tmpList), 123);
113 | }
114 |
115 | }
116 |
117 | private OnAsrListener onAsrListener = new OnAsrListener() {
118 | @Override
119 | public void onAsrReady() {
120 |
121 | }
122 |
123 | @Override
124 | public void onAsrBegin() {
125 |
126 | }
127 |
128 | @Override
129 | public void onAsrEnd() {
130 |
131 | }
132 |
133 | @Override
134 | public void onAsrPartialResult(String[] results, RecogResult recogResult) {
135 |
136 | }
137 |
138 | @Override
139 | public void onAsrOnlineNluResult(String nluResult) {
140 |
141 | }
142 |
143 | @Override
144 | public void onAsrFinalResult(String[] results, RecogResult recogResult) {
145 | if (resultStateful != null) {
146 | resultStateful.success(results[0]);
147 | }
148 | }
149 |
150 | @Override
151 | public void onAsrFinish(RecogResult recogResult) {
152 |
153 | }
154 |
155 | @Override
156 | public void onAsrFinishError(int errorCode, int subErrorCode, String descMessage, RecogResult recogResult) {
157 | if (resultStateful != null) {
158 | resultStateful.error(descMessage, null, null);
159 | }
160 | }
161 |
162 | @Override
163 | public void onAsrLongFinish() {
164 |
165 | }
166 |
167 | @Override
168 | public void onAsrVolume(int volumePercent, int volume) {
169 |
170 | }
171 |
172 | @Override
173 | public void onAsrAudio(byte[] data, int offset, int length) {
174 |
175 | }
176 |
177 | @Override
178 | public void onAsrExit() {
179 |
180 | }
181 |
182 | @Override
183 | public void onOfflineLoaded() {
184 |
185 | }
186 |
187 | @Override
188 | public void onOfflineUnLoaded() {
189 |
190 | }
191 | };
192 | }
193 |
--------------------------------------------------------------------------------
/android/asr_plugin/src/main/java/com/example/asr_plugin/OnAsrListener.java:
--------------------------------------------------------------------------------
1 | package com.example.asr_plugin;
2 |
3 | /**
4 | * 与SDK中回调参数的对应关系定义在RecogEventAdapter类中
5 | */
6 |
7 | public interface OnAsrListener {
8 |
9 | /**
10 | * CALLBACK_EVENT_ASR_READY
11 | * ASR_START 输入事件调用后,引擎准备完毕
12 | */
13 | void onAsrReady();
14 |
15 | /**
16 | * CALLBACK_EVENT_ASR_BEGIN
17 | * onAsrReady后检查到用户开始说话
18 | */
19 | void onAsrBegin();
20 |
21 | /**
22 | * CALLBACK_EVENT_ASR_END
23 | * 检查到用户开始说话停止,或者ASR_STOP 输入事件调用后,
24 | */
25 | void onAsrEnd();
26 |
27 | /**
28 | * CALLBACK_EVENT_ASR_PARTIAL resultType=partial_result
29 | * onAsrBegin 后 随着用户的说话,返回的临时结果
30 | *
31 | * @param results 可能返回多个结果,请取第一个结果
32 | * @param recogResult 完整的结果
33 | */
34 | void onAsrPartialResult(String[] results, RecogResult recogResult);
35 |
36 | /**
37 | * 语音的在线语义结果
38 | *
39 | * CALLBACK_EVENT_ASR_PARTIAL resultType=nlu_result
40 | *
41 | * @param nluResult
42 | */
43 | void onAsrOnlineNluResult(String nluResult);
44 |
45 | /**
46 | * CALLBACK_EVENT_ASR_PARTIAL resultType=final_result
47 | * 最终的识别结果
48 | *
49 | * @param results 可能返回多个结果,请取第一个结果
50 | * @param recogResult 完整的结果
51 | */
52 | void onAsrFinalResult(String[] results, RecogResult recogResult);
53 |
54 | /**
55 | * CALLBACK_EVENT_ASR_FINISH
56 | *
57 | * @param recogResult 完整的结果
58 | */
59 | void onAsrFinish(RecogResult recogResult);
60 |
61 | /**
62 | * CALLBACK_EVENT_ASR_FINISH error!=0
63 | *
64 | * @param errorCode
65 | * @param subErrorCode
66 | * @param descMessage
67 | * @param recogResult
68 | */
69 | void onAsrFinishError(int errorCode, int subErrorCode, String descMessage, RecogResult recogResult);
70 |
71 | /**
72 | * 长语音识别结束
73 | */
74 | void onAsrLongFinish();
75 |
76 | /**
77 | * CALLBACK_EVENT_ASR_VOLUME
78 | * 音量回调
79 | *
80 | * @param volumePercent 音量的相对值,百分比,0-100
81 | * @param volume 音量绝对值
82 | */
83 | void onAsrVolume(int volumePercent, int volume);
84 |
85 | /**
86 | * CALLBACK_EVENT_ASR_AUDIO
87 | *
88 | * @param data pcm格式,16bits 16000采样率
89 | * @param offset
90 | * @param length
91 | */
92 | void onAsrAudio(byte[] data, int offset, int length);
93 |
94 | /**
95 | * CALLBACK_EVENT_ASR_EXIT
96 | * 引擎完成整个识别,空闲中
97 | */
98 | void onAsrExit();
99 |
100 | /**
101 | * CALLBACK_EVENT_ASR_LOADED
102 | * 离线命令词资源加载成功
103 | */
104 | void onOfflineLoaded();
105 |
106 | /**
107 | * CALLBACK_EVENT_ASR_UNLOADED
108 | * 离线命令词资源释放成功
109 | */
110 | void onOfflineUnLoaded();
111 | }
112 |
--------------------------------------------------------------------------------
/android/asr_plugin/src/main/java/com/example/asr_plugin/RecogEventAdapter.java:
--------------------------------------------------------------------------------
1 | package com.example.asr_plugin;
2 |
3 | import android.util.Log;
4 |
5 | import com.baidu.speech.EventListener;
6 | import com.baidu.speech.asr.SpeechConstant;
7 |
8 | import org.json.JSONException;
9 | import org.json.JSONObject;
10 |
11 | /**
12 | * Created by fujiayi on 2017/6/14.
13 | */
14 |
15 | public class RecogEventAdapter implements EventListener {
16 |
17 | private OnAsrListener listener;
18 |
19 | private static final String TAG = "RecogEventAdapter";
20 |
21 | public RecogEventAdapter(OnAsrListener listener) {
22 | this.listener = listener;
23 | }
24 |
25 | // 基于DEMO集成3.1 开始回调事件
26 | @Override
27 | public void onEvent(String name, String params, byte[] data, int offset, int length) {
28 | String currentJson = params;
29 | String logMessage = "name:" + name + "; params:" + params;
30 |
31 | // logcat 中 搜索RecogEventAdapter,即可以看见下面一行的日志
32 | Log.i(TAG, logMessage);
33 | if (false) { // 可以调试,不需要后续逻辑
34 | return;
35 | }
36 | if (name.equals(SpeechConstant.CALLBACK_EVENT_ASR_LOADED)) {
37 | listener.onOfflineLoaded();
38 | } else if (name.equals(SpeechConstant.CALLBACK_EVENT_ASR_UNLOADED)) {
39 | listener.onOfflineUnLoaded();
40 | } else if (name.equals(SpeechConstant.CALLBACK_EVENT_ASR_READY)) {
41 | // 引擎准备就绪,可以开始说话
42 | listener.onAsrReady();
43 | } else if (name.equals(SpeechConstant.CALLBACK_EVENT_ASR_BEGIN)) {
44 | // 检测到用户的已经开始说话
45 | listener.onAsrBegin();
46 |
47 | } else if (name.equals(SpeechConstant.CALLBACK_EVENT_ASR_END)) {
48 | // 检测到用户的已经停止说话
49 | listener.onAsrEnd();
50 |
51 | } else if (name.equals(SpeechConstant.CALLBACK_EVENT_ASR_PARTIAL)) {
52 | RecogResult recogResult = RecogResult.parseJson(params);
53 | // 临时识别结果, 长语音模式需要从此消息中取出结果
54 | String[] results = recogResult.getResultsRecognition();
55 | if (recogResult.isFinalResult()) {
56 | listener.onAsrFinalResult(results, recogResult);
57 | } else if (recogResult.isPartialResult()) {
58 | listener.onAsrPartialResult(results, recogResult);
59 | } else if (recogResult.isNluResult()) {
60 | listener.onAsrOnlineNluResult(new String(data, offset, length));
61 | }
62 |
63 | } else if (name.equals(SpeechConstant.CALLBACK_EVENT_ASR_FINISH)) {
64 | // 识别结束, 最终识别结果或可能的错误
65 | RecogResult recogResult = RecogResult.parseJson(params);
66 | if (recogResult.hasError()) {
67 | int errorCode = recogResult.getError();
68 | int subErrorCode = recogResult.getSubError();
69 | Log.e(TAG, "asr error:" + params);
70 | listener.onAsrFinishError(errorCode, subErrorCode, recogResult.getDesc(), recogResult);
71 | } else {
72 | listener.onAsrFinish(recogResult);
73 | }
74 | } else if (name.equals(SpeechConstant.CALLBACK_EVENT_ASR_LONG_SPEECH)) { // 长语音
75 | listener.onAsrLongFinish(); // 长语音
76 | } else if (name.equals(SpeechConstant.CALLBACK_EVENT_ASR_EXIT)) {
77 | listener.onAsrExit();
78 | } else if (name.equals(SpeechConstant.CALLBACK_EVENT_ASR_VOLUME)) {
79 | // Logger.info(TAG, "asr volume event:" + params);
80 | Volume vol = parseVolumeJson(params);
81 | listener.onAsrVolume(vol.volumePercent, vol.volume);
82 | } else if (name.equals(SpeechConstant.CALLBACK_EVENT_ASR_AUDIO)) {
83 | if (data.length != length) {
84 | Log.e(TAG, "internal error: asr.audio callback data length is not equal to length param");
85 | }
86 | listener.onAsrAudio(data, offset, length);
87 | }
88 | }
89 |
90 | private Volume parseVolumeJson(String jsonStr) {
91 | Volume vol = new Volume();
92 | vol.origalJson = jsonStr;
93 | try {
94 | JSONObject json = new JSONObject(jsonStr);
95 | vol.volumePercent = json.getInt("volume-percent");
96 | vol.volume = json.getInt("volume");
97 | } catch (JSONException e) {
98 | e.printStackTrace();
99 | }
100 | return vol;
101 | }
102 |
103 | private class Volume {
104 | private int volumePercent = -1;
105 | private int volume = -1;
106 | private String origalJson;
107 | }
108 |
109 | }
110 |
--------------------------------------------------------------------------------
/android/asr_plugin/src/main/java/com/example/asr_plugin/RecogResult.java:
--------------------------------------------------------------------------------
1 | package com.example.asr_plugin;
2 |
3 | import org.json.JSONArray;
4 | import org.json.JSONException;
5 | import org.json.JSONObject;
6 |
7 | /**
8 | * Created by fujiayi on 2017/6/24.
9 | */
10 | public class RecogResult {
11 | private static final int ERROR_NONE = 0;
12 |
13 | private String origalJson;
14 | private String[] resultsRecognition;
15 | private String origalResult;
16 | private String sn; // 日志id, 请求有问题请提问带上sn
17 | private String desc;
18 | private String resultType;
19 | private int error = -1;
20 | private int subError = -1;
21 |
22 | public static RecogResult parseJson(String jsonStr) {
23 | RecogResult result = new RecogResult();
24 | result.setOrigalJson(jsonStr);
25 | try {
26 | JSONObject json = new JSONObject(jsonStr);
27 | int error = json.optInt("error");
28 | int subError = json.optInt("sub_error");
29 | result.setError(error);
30 | result.setDesc(json.optString("desc"));
31 | result.setResultType(json.optString("result_type"));
32 | result.setSubError(subError);
33 | if (error == ERROR_NONE) {
34 | result.setOrigalResult(json.getString("origin_result"));
35 | JSONArray arr = json.optJSONArray("results_recognition");
36 | if (arr != null) {
37 | int size = arr.length();
38 | String[] recogs = new String[size];
39 | for (int i = 0; i < size; i++) {
40 | recogs[i] = arr.getString(i);
41 | }
42 | result.setResultsRecognition(recogs);
43 | }
44 |
45 |
46 | }
47 | } catch (JSONException e) {
48 | e.printStackTrace();
49 | }
50 |
51 | return result;
52 | }
53 |
54 | public boolean hasError() {
55 | return error != ERROR_NONE;
56 | }
57 |
58 | public boolean isFinalResult() {
59 | return "final_result".equals(resultType);
60 | }
61 |
62 |
63 | public boolean isPartialResult() {
64 | return "partial_result".equals(resultType);
65 | }
66 |
67 | public boolean isNluResult() {
68 | return "nlu_result".equals(resultType);
69 | }
70 |
71 | public String getOrigalJson() {
72 | return origalJson;
73 | }
74 |
75 | public void setOrigalJson(String origalJson) {
76 | this.origalJson = origalJson;
77 | }
78 |
79 | public String[] getResultsRecognition() {
80 | return resultsRecognition;
81 | }
82 |
83 | public void setResultsRecognition(String[] resultsRecognition) {
84 | this.resultsRecognition = resultsRecognition;
85 | }
86 |
87 | public String getSn() {
88 | return sn;
89 | }
90 |
91 | public void setSn(String sn) {
92 | this.sn = sn;
93 | }
94 |
95 | public int getError() {
96 | return error;
97 | }
98 |
99 | public void setError(int error) {
100 | this.error = error;
101 | }
102 |
103 | public String getDesc() {
104 | return desc;
105 | }
106 |
107 | public void setDesc(String desc) {
108 | this.desc = desc;
109 | }
110 |
111 | public String getOrigalResult() {
112 | return origalResult;
113 | }
114 |
115 | public void setOrigalResult(String origalResult) {
116 | this.origalResult = origalResult;
117 | }
118 |
119 | public String getResultType() {
120 | return resultType;
121 | }
122 |
123 | public void setResultType(String resultType) {
124 | this.resultType = resultType;
125 | }
126 |
127 | public int getSubError() {
128 | return subError;
129 | }
130 |
131 | public void setSubError(int subError) {
132 | this.subError = subError;
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/android/asr_plugin/src/main/java/com/example/asr_plugin/ResultStateful.java:
--------------------------------------------------------------------------------
1 | package com.example.asr_plugin;
2 |
3 | import android.util.Log;
4 |
5 | import androidx.annotation.Nullable;
6 |
7 | import io.flutter.plugin.common.MethodChannel;
8 |
9 | public class ResultStateful implements MethodChannel.Result {
10 | private final static String TAG = "ResultStateful";
11 | private MethodChannel.Result result;
12 | private boolean called;
13 |
14 | public static ResultStateful of(MethodChannel.Result result) {
15 | return new ResultStateful(result);
16 | }
17 |
18 | private ResultStateful(MethodChannel.Result result) {
19 | this.result = result;
20 | }
21 |
22 | @Override
23 | public void success(@Nullable Object o) {
24 | if (called) {
25 | printError();
26 | return;
27 | }
28 | called = true;
29 | result.success(o);
30 | }
31 |
32 | @Override
33 | public void error(String s, @Nullable String s1, @Nullable Object o) {
34 | if (called) {
35 | printError();
36 | return;
37 | }
38 | called = true;
39 | result.error(s, s1, o);
40 | }
41 |
42 | @Override
43 | public void notImplemented() {
44 | if (called) {
45 | printError();
46 | return;
47 | }
48 | called = true;
49 | result.notImplemented();
50 |
51 | }
52 |
53 | private void printError() {
54 | Log.e(TAG, "error:result called");
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/android/asr_plugin/src/main/jniLibs/arm64-v8a/libBaiduSpeechSDK.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/android/asr_plugin/src/main/jniLibs/arm64-v8a/libBaiduSpeechSDK.so
--------------------------------------------------------------------------------
/android/asr_plugin/src/main/jniLibs/arm64-v8a/libvad.dnn.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/android/asr_plugin/src/main/jniLibs/arm64-v8a/libvad.dnn.so
--------------------------------------------------------------------------------
/android/asr_plugin/src/main/jniLibs/armeabi-v7a/libBaiduSpeechSDK.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/android/asr_plugin/src/main/jniLibs/armeabi-v7a/libBaiduSpeechSDK.so
--------------------------------------------------------------------------------
/android/asr_plugin/src/main/jniLibs/armeabi-v7a/libvad.dnn.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/android/asr_plugin/src/main/jniLibs/armeabi-v7a/libvad.dnn.so
--------------------------------------------------------------------------------
/android/asr_plugin/src/main/jniLibs/x86/libBaiduSpeechSDK.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/android/asr_plugin/src/main/jniLibs/x86/libBaiduSpeechSDK.so
--------------------------------------------------------------------------------
/android/asr_plugin/src/main/jniLibs/x86/libvad.dnn.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/android/asr_plugin/src/main/jniLibs/x86/libvad.dnn.so
--------------------------------------------------------------------------------
/android/asr_plugin/src/main/jniLibs/x86_64/libBaiduSpeechSDK.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/android/asr_plugin/src/main/jniLibs/x86_64/libBaiduSpeechSDK.so
--------------------------------------------------------------------------------
/android/asr_plugin/src/main/jniLibs/x86_64/libvad.dnn.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/android/asr_plugin/src/main/jniLibs/x86_64/libvad.dnn.so
--------------------------------------------------------------------------------
/android/asr_plugin/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | asr_plugin
3 |
4 |
--------------------------------------------------------------------------------
/android/asr_plugin/src/test/java/com/example/asr_plugin/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.example.asr_plugin;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.3.50'
3 | repositories {
4 | google()
5 | jcenter()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:3.3.0'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | google()
17 | jcenter()
18 | }
19 | }
20 |
21 | rootProject.buildDir = '../build'
22 | subprojects {
23 | project.buildDir = "${rootProject.buildDir}/${project.name}"
24 | }
25 | subprojects {
26 | project.evaluationDependsOn(':app')
27 | }
28 |
29 | task clean(type: Delete) {
30 | delete rootProject.buildDir
31 | }
32 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.enableR8=true
3 | android.useAndroidX=true
4 | android.enableJetifier=true
5 |
6 |
7 | MYAPP_RELEASE_STORE_FILE=key.jks
8 | MYAPP_RELEASE_KEY_ALIAS=key
9 | MYAPP_RELEASE_STORE_PASSWORD=junwei214
10 | MYAPP_RELEASE_KEY_PASSWORD=junwei214
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jan 31 10:48:39 CST 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
7 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':asr_plugin'
2 |
3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
4 |
5 | def plugins = new Properties()
6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
7 | if (pluginsFile.exists()) {
8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
9 | }
10 |
11 | plugins.each { name, path ->
12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
13 | include ":$name"
14 | project(":$name").projectDir = pluginDirectory
15 | }
16 |
17 | //setBinding(new Binding([gradle: this]))
18 |
19 | //evaluate(new File(
20 | // settingsDir.parentFile,
21 | // 'flutter_module/.android/include_flutter.groovy'
22 | //))
23 |
--------------------------------------------------------------------------------
/assets/data/cities.json:
--------------------------------------------------------------------------------
1 | [{"code":"1301","name":"石家庄市","provinceCode":"13"},{"code":"1302","name":"唐山市","provinceCode":"13"},{"code":"1303","name":"秦皇岛市","provinceCode":"13"},{"code":"1304","name":"邯郸市","provinceCode":"13"},{"code":"1305","name":"邢台市","provinceCode":"13"},{"code":"1306","name":"保定市","provinceCode":"13"},{"code":"1307","name":"张家口市","provinceCode":"13"},{"code":"1308","name":"承德市","provinceCode":"13"},{"code":"1309","name":"沧州市","provinceCode":"13"},{"code":"1310","name":"廊坊市","provinceCode":"13"},{"code":"1311","name":"衡水市","provinceCode":"13"},{"code":"1401","name":"太原市","provinceCode":"14"},{"code":"1402","name":"大同市","provinceCode":"14"},{"code":"1403","name":"阳泉市","provinceCode":"14"},{"code":"1404","name":"长治市","provinceCode":"14"},{"code":"1405","name":"晋城市","provinceCode":"14"},{"code":"1406","name":"朔州市","provinceCode":"14"},{"code":"1407","name":"晋中市","provinceCode":"14"},{"code":"1408","name":"运城市","provinceCode":"14"},{"code":"1409","name":"忻州市","provinceCode":"14"},{"code":"1410","name":"临汾市","provinceCode":"14"},{"code":"1411","name":"吕梁市","provinceCode":"14"},{"code":"1501","name":"呼和浩特市","provinceCode":"15"},{"code":"1502","name":"包头市","provinceCode":"15"},{"code":"1503","name":"乌海市","provinceCode":"15"},{"code":"1504","name":"赤峰市","provinceCode":"15"},{"code":"1505","name":"通辽市","provinceCode":"15"},{"code":"1506","name":"鄂尔多斯市","provinceCode":"15"},{"code":"1507","name":"呼伦贝尔市","provinceCode":"15"},{"code":"1508","name":"巴彦淖尔市","provinceCode":"15"},{"code":"1509","name":"乌兰察布市","provinceCode":"15"},{"code":"1522","name":"兴安盟","provinceCode":"15"},{"code":"1525","name":"锡林郭勒盟","provinceCode":"15"},{"code":"1529","name":"阿拉善盟","provinceCode":"15"},{"code":"2101","name":"沈阳市","provinceCode":"21"},{"code":"2102","name":"大连市","provinceCode":"21"},{"code":"2103","name":"鞍山市","provinceCode":"21"},{"code":"2104","name":"抚顺市","provinceCode":"21"},{"code":"2105","name":"本溪市","provinceCode":"21"},{"code":"2106","name":"丹东市","provinceCode":"21"},{"code":"2107","name":"锦州市","provinceCode":"21"},{"code":"2108","name":"营口市","provinceCode":"21"},{"code":"2109","name":"阜新市","provinceCode":"21"},{"code":"2110","name":"辽阳市","provinceCode":"21"},{"code":"2111","name":"盘锦市","provinceCode":"21"},{"code":"2112","name":"铁岭市","provinceCode":"21"},{"code":"2113","name":"朝阳市","provinceCode":"21"},{"code":"2114","name":"葫芦岛市","provinceCode":"21"},{"code":"2201","name":"长春市","provinceCode":"22"},{"code":"2202","name":"吉林市","provinceCode":"22"},{"code":"2203","name":"四平市","provinceCode":"22"},{"code":"2204","name":"辽源市","provinceCode":"22"},{"code":"2205","name":"通化市","provinceCode":"22"},{"code":"2206","name":"白山市","provinceCode":"22"},{"code":"2207","name":"松原市","provinceCode":"22"},{"code":"2208","name":"白城市","provinceCode":"22"},{"code":"2224","name":"延边朝鲜族自治州","provinceCode":"22"},{"code":"2301","name":"哈尔滨市","provinceCode":"23"},{"code":"2302","name":"齐齐哈尔市","provinceCode":"23"},{"code":"2303","name":"鸡西市","provinceCode":"23"},{"code":"2304","name":"鹤岗市","provinceCode":"23"},{"code":"2305","name":"双鸭山市","provinceCode":"23"},{"code":"2306","name":"大庆市","provinceCode":"23"},{"code":"2307","name":"伊春市","provinceCode":"23"},{"code":"2308","name":"佳木斯市","provinceCode":"23"},{"code":"2309","name":"七台河市","provinceCode":"23"},{"code":"2310","name":"牡丹江市","provinceCode":"23"},{"code":"2311","name":"黑河市","provinceCode":"23"},{"code":"2312","name":"绥化市","provinceCode":"23"},{"code":"2327","name":"大兴安岭地区","provinceCode":"23"},{"code":"3201","name":"南京市","provinceCode":"32"},{"code":"3202","name":"无锡市","provinceCode":"32"},{"code":"3203","name":"徐州市","provinceCode":"32"},{"code":"3204","name":"常州市","provinceCode":"32"},{"code":"3205","name":"苏州市","provinceCode":"32"},{"code":"3206","name":"南通市","provinceCode":"32"},{"code":"3207","name":"连云港市","provinceCode":"32"},{"code":"3208","name":"淮安市","provinceCode":"32"},{"code":"3209","name":"盐城市","provinceCode":"32"},{"code":"3210","name":"扬州市","provinceCode":"32"},{"code":"3211","name":"镇江市","provinceCode":"32"},{"code":"3212","name":"泰州市","provinceCode":"32"},{"code":"3213","name":"宿迁市","provinceCode":"32"},{"code":"3301","name":"杭州市","provinceCode":"33"},{"code":"3302","name":"宁波市","provinceCode":"33"},{"code":"3303","name":"温州市","provinceCode":"33"},{"code":"3304","name":"嘉兴市","provinceCode":"33"},{"code":"3305","name":"湖州市","provinceCode":"33"},{"code":"3306","name":"绍兴市","provinceCode":"33"},{"code":"3307","name":"金华市","provinceCode":"33"},{"code":"3308","name":"衢州市","provinceCode":"33"},{"code":"3309","name":"舟山市","provinceCode":"33"},{"code":"3310","name":"台州市","provinceCode":"33"},{"code":"3311","name":"丽水市","provinceCode":"33"},{"code":"3401","name":"合肥市","provinceCode":"34"},{"code":"3402","name":"芜湖市","provinceCode":"34"},{"code":"3403","name":"蚌埠市","provinceCode":"34"},{"code":"3404","name":"淮南市","provinceCode":"34"},{"code":"3405","name":"马鞍山市","provinceCode":"34"},{"code":"3406","name":"淮北市","provinceCode":"34"},{"code":"3407","name":"铜陵市","provinceCode":"34"},{"code":"3408","name":"安庆市","provinceCode":"34"},{"code":"3410","name":"黄山市","provinceCode":"34"},{"code":"3411","name":"滁州市","provinceCode":"34"},{"code":"3412","name":"阜阳市","provinceCode":"34"},{"code":"3413","name":"宿州市","provinceCode":"34"},{"code":"3415","name":"六安市","provinceCode":"34"},{"code":"3416","name":"亳州市","provinceCode":"34"},{"code":"3417","name":"池州市","provinceCode":"34"},{"code":"3418","name":"宣城市","provinceCode":"34"},{"code":"3501","name":"福州市","provinceCode":"35"},{"code":"3502","name":"厦门市","provinceCode":"35"},{"code":"3503","name":"莆田市","provinceCode":"35"},{"code":"3504","name":"三明市","provinceCode":"35"},{"code":"3505","name":"泉州市","provinceCode":"35"},{"code":"3506","name":"漳州市","provinceCode":"35"},{"code":"3507","name":"南平市","provinceCode":"35"},{"code":"3508","name":"龙岩市","provinceCode":"35"},{"code":"3509","name":"宁德市","provinceCode":"35"},{"code":"3601","name":"南昌市","provinceCode":"36"},{"code":"3602","name":"景德镇市","provinceCode":"36"},{"code":"3603","name":"萍乡市","provinceCode":"36"},{"code":"3604","name":"九江市","provinceCode":"36"},{"code":"3605","name":"新余市","provinceCode":"36"},{"code":"3606","name":"鹰潭市","provinceCode":"36"},{"code":"3607","name":"赣州市","provinceCode":"36"},{"code":"3608","name":"吉安市","provinceCode":"36"},{"code":"3609","name":"宜春市","provinceCode":"36"},{"code":"3610","name":"抚州市","provinceCode":"36"},{"code":"3611","name":"上饶市","provinceCode":"36"},{"code":"3701","name":"济南市","provinceCode":"37"},{"code":"3702","name":"青岛市","provinceCode":"37"},{"code":"3703","name":"淄博市","provinceCode":"37"},{"code":"3704","name":"枣庄市","provinceCode":"37"},{"code":"3705","name":"东营市","provinceCode":"37"},{"code":"3706","name":"烟台市","provinceCode":"37"},{"code":"3707","name":"潍坊市","provinceCode":"37"},{"code":"3708","name":"济宁市","provinceCode":"37"},{"code":"3709","name":"泰安市","provinceCode":"37"},{"code":"3710","name":"威海市","provinceCode":"37"},{"code":"3711","name":"日照市","provinceCode":"37"},{"code":"3712","name":"莱芜市","provinceCode":"37"},{"code":"3713","name":"临沂市","provinceCode":"37"},{"code":"3714","name":"德州市","provinceCode":"37"},{"code":"3715","name":"聊城市","provinceCode":"37"},{"code":"3716","name":"滨州市","provinceCode":"37"},{"code":"3717","name":"菏泽市","provinceCode":"37"},{"code":"4101","name":"郑州市","provinceCode":"41"},{"code":"4102","name":"开封市","provinceCode":"41"},{"code":"4103","name":"洛阳市","provinceCode":"41"},{"code":"4104","name":"平顶山市","provinceCode":"41"},{"code":"4105","name":"安阳市","provinceCode":"41"},{"code":"4106","name":"鹤壁市","provinceCode":"41"},{"code":"4107","name":"新乡市","provinceCode":"41"},{"code":"4108","name":"焦作市","provinceCode":"41"},{"code":"4109","name":"濮阳市","provinceCode":"41"},{"code":"4110","name":"许昌市","provinceCode":"41"},{"code":"4111","name":"漯河市","provinceCode":"41"},{"code":"4112","name":"三门峡市","provinceCode":"41"},{"code":"4113","name":"南阳市","provinceCode":"41"},{"code":"4114","name":"商丘市","provinceCode":"41"},{"code":"4115","name":"信阳市","provinceCode":"41"},{"code":"4116","name":"周口市","provinceCode":"41"},{"code":"4117","name":"驻马店市","provinceCode":"41"},{"code":"4201","name":"武汉市","provinceCode":"42"},{"code":"4202","name":"黄石市","provinceCode":"42"},{"code":"4203","name":"十堰市","provinceCode":"42"},{"code":"4205","name":"宜昌市","provinceCode":"42"},{"code":"4206","name":"襄阳市","provinceCode":"42"},{"code":"4207","name":"鄂州市","provinceCode":"42"},{"code":"4208","name":"荆门市","provinceCode":"42"},{"code":"4209","name":"孝感市","provinceCode":"42"},{"code":"4210","name":"荆州市","provinceCode":"42"},{"code":"4211","name":"黄冈市","provinceCode":"42"},{"code":"4212","name":"咸宁市","provinceCode":"42"},{"code":"4213","name":"随州市","provinceCode":"42"},{"code":"4228","name":"恩施土家族苗族自治州","provinceCode":"42"},{"code":"4301","name":"长沙市","provinceCode":"43"},{"code":"4302","name":"株洲市","provinceCode":"43"},{"code":"4303","name":"湘潭市","provinceCode":"43"},{"code":"4304","name":"衡阳市","provinceCode":"43"},{"code":"4305","name":"邵阳市","provinceCode":"43"},{"code":"4306","name":"岳阳市","provinceCode":"43"},{"code":"4307","name":"常德市","provinceCode":"43"},{"code":"4308","name":"张家界市","provinceCode":"43"},{"code":"4309","name":"益阳市","provinceCode":"43"},{"code":"4310","name":"郴州市","provinceCode":"43"},{"code":"4311","name":"永州市","provinceCode":"43"},{"code":"4312","name":"怀化市","provinceCode":"43"},{"code":"4313","name":"娄底市","provinceCode":"43"},{"code":"4331","name":"湘西土家族苗族自治州","provinceCode":"43"},{"code":"4401","name":"广州市","provinceCode":"44"},{"code":"4402","name":"韶关市","provinceCode":"44"},{"code":"4403","name":"深圳市","provinceCode":"44"},{"code":"4404","name":"珠海市","provinceCode":"44"},{"code":"4405","name":"汕头市","provinceCode":"44"},{"code":"4406","name":"佛山市","provinceCode":"44"},{"code":"4407","name":"江门市","provinceCode":"44"},{"code":"4408","name":"湛江市","provinceCode":"44"},{"code":"4409","name":"茂名市","provinceCode":"44"},{"code":"4412","name":"肇庆市","provinceCode":"44"},{"code":"4413","name":"惠州市","provinceCode":"44"},{"code":"4414","name":"梅州市","provinceCode":"44"},{"code":"4415","name":"汕尾市","provinceCode":"44"},{"code":"4416","name":"河源市","provinceCode":"44"},{"code":"4417","name":"阳江市","provinceCode":"44"},{"code":"4418","name":"清远市","provinceCode":"44"},{"code":"4419","name":"东莞市","provinceCode":"44"},{"code":"4420","name":"中山市","provinceCode":"44"},{"code":"4451","name":"潮州市","provinceCode":"44"},{"code":"4452","name":"揭阳市","provinceCode":"44"},{"code":"4453","name":"云浮市","provinceCode":"44"},{"code":"4501","name":"南宁市","provinceCode":"45"},{"code":"4502","name":"柳州市","provinceCode":"45"},{"code":"4503","name":"桂林市","provinceCode":"45"},{"code":"4504","name":"梧州市","provinceCode":"45"},{"code":"4505","name":"北海市","provinceCode":"45"},{"code":"4506","name":"防城港市","provinceCode":"45"},{"code":"4507","name":"钦州市","provinceCode":"45"},{"code":"4508","name":"贵港市","provinceCode":"45"},{"code":"4509","name":"玉林市","provinceCode":"45"},{"code":"4510","name":"百色市","provinceCode":"45"},{"code":"4511","name":"贺州市","provinceCode":"45"},{"code":"4512","name":"河池市","provinceCode":"45"},{"code":"4513","name":"来宾市","provinceCode":"45"},{"code":"4514","name":"崇左市","provinceCode":"45"},{"code":"4601","name":"海口市","provinceCode":"46"},{"code":"4602","name":"三亚市","provinceCode":"46"},{"code":"4603","name":"三沙市","provinceCode":"46"},{"code":"4604","name":"儋州市","provinceCode":"46"},{"code":"5002","name":"县","provinceCode":"50"},{"code":"5101","name":"成都市","provinceCode":"51"},{"code":"5103","name":"自贡市","provinceCode":"51"},{"code":"5104","name":"攀枝花市","provinceCode":"51"},{"code":"5105","name":"泸州市","provinceCode":"51"},{"code":"5106","name":"德阳市","provinceCode":"51"},{"code":"5107","name":"绵阳市","provinceCode":"51"},{"code":"5108","name":"广元市","provinceCode":"51"},{"code":"5109","name":"遂宁市","provinceCode":"51"},{"code":"5110","name":"内江市","provinceCode":"51"},{"code":"5111","name":"乐山市","provinceCode":"51"},{"code":"5113","name":"南充市","provinceCode":"51"},{"code":"5114","name":"眉山市","provinceCode":"51"},{"code":"5115","name":"宜宾市","provinceCode":"51"},{"code":"5116","name":"广安市","provinceCode":"51"},{"code":"5117","name":"达州市","provinceCode":"51"},{"code":"5118","name":"雅安市","provinceCode":"51"},{"code":"5119","name":"巴中市","provinceCode":"51"},{"code":"5120","name":"资阳市","provinceCode":"51"},{"code":"5132","name":"阿坝藏族羌族自治州","provinceCode":"51"},{"code":"5133","name":"甘孜藏族自治州","provinceCode":"51"},{"code":"5134","name":"凉山彝族自治州","provinceCode":"51"},{"code":"5201","name":"贵阳市","provinceCode":"52"},{"code":"5202","name":"六盘水市","provinceCode":"52"},{"code":"5203","name":"遵义市","provinceCode":"52"},{"code":"5204","name":"安顺市","provinceCode":"52"},{"code":"5205","name":"毕节市","provinceCode":"52"},{"code":"5206","name":"铜仁市","provinceCode":"52"},{"code":"5223","name":"黔西南布依族苗族自治州","provinceCode":"52"},{"code":"5226","name":"黔东南苗族侗族自治州","provinceCode":"52"},{"code":"5227","name":"黔南布依族苗族自治州","provinceCode":"52"},{"code":"5301","name":"昆明市","provinceCode":"53"},{"code":"5303","name":"曲靖市","provinceCode":"53"},{"code":"5304","name":"玉溪市","provinceCode":"53"},{"code":"5305","name":"保山市","provinceCode":"53"},{"code":"5306","name":"昭通市","provinceCode":"53"},{"code":"5307","name":"丽江市","provinceCode":"53"},{"code":"5308","name":"普洱市","provinceCode":"53"},{"code":"5309","name":"临沧市","provinceCode":"53"},{"code":"5323","name":"楚雄彝族自治州","provinceCode":"53"},{"code":"5325","name":"红河哈尼族彝族自治州","provinceCode":"53"},{"code":"5326","name":"文山壮族苗族自治州","provinceCode":"53"},{"code":"5328","name":"西双版纳傣族自治州","provinceCode":"53"},{"code":"5329","name":"大理白族自治州","provinceCode":"53"},{"code":"5331","name":"德宏傣族景颇族自治州","provinceCode":"53"},{"code":"5333","name":"怒江傈僳族自治州","provinceCode":"53"},{"code":"5334","name":"迪庆藏族自治州","provinceCode":"53"},{"code":"5401","name":"拉萨市","provinceCode":"54"},{"code":"5402","name":"日喀则市","provinceCode":"54"},{"code":"5403","name":"昌都市","provinceCode":"54"},{"code":"5404","name":"林芝市","provinceCode":"54"},{"code":"5405","name":"山南市","provinceCode":"54"},{"code":"5424","name":"那曲地区","provinceCode":"54"},{"code":"5425","name":"阿里地区","provinceCode":"54"},{"code":"6101","name":"西安市","provinceCode":"61"},{"code":"6102","name":"铜川市","provinceCode":"61"},{"code":"6103","name":"宝鸡市","provinceCode":"61"},{"code":"6104","name":"咸阳市","provinceCode":"61"},{"code":"6105","name":"渭南市","provinceCode":"61"},{"code":"6106","name":"延安市","provinceCode":"61"},{"code":"6107","name":"汉中市","provinceCode":"61"},{"code":"6108","name":"榆林市","provinceCode":"61"},{"code":"6109","name":"安康市","provinceCode":"61"},{"code":"6110","name":"商洛市","provinceCode":"61"},{"code":"6201","name":"兰州市","provinceCode":"62"},{"code":"6202","name":"嘉峪关市","provinceCode":"62"},{"code":"6203","name":"金昌市","provinceCode":"62"},{"code":"6204","name":"白银市","provinceCode":"62"},{"code":"6205","name":"天水市","provinceCode":"62"},{"code":"6206","name":"武威市","provinceCode":"62"},{"code":"6207","name":"张掖市","provinceCode":"62"},{"code":"6208","name":"平凉市","provinceCode":"62"},{"code":"6209","name":"酒泉市","provinceCode":"62"},{"code":"6210","name":"庆阳市","provinceCode":"62"},{"code":"6211","name":"定西市","provinceCode":"62"},{"code":"6212","name":"陇南市","provinceCode":"62"},{"code":"6229","name":"临夏回族自治州","provinceCode":"62"},{"code":"6230","name":"甘南藏族自治州","provinceCode":"62"},{"code":"6301","name":"西宁市","provinceCode":"63"},{"code":"6302","name":"海东市","provinceCode":"63"},{"code":"6322","name":"海北藏族自治州","provinceCode":"63"},{"code":"6323","name":"黄南藏族自治州","provinceCode":"63"},{"code":"6325","name":"海南藏族自治州","provinceCode":"63"},{"code":"6326","name":"果洛藏族自治州","provinceCode":"63"},{"code":"6327","name":"玉树藏族自治州","provinceCode":"63"},{"code":"6328","name":"海西蒙古族藏族自治州","provinceCode":"63"},{"code":"6401","name":"银川市","provinceCode":"64"},{"code":"6402","name":"石嘴山市","provinceCode":"64"},{"code":"6403","name":"吴忠市","provinceCode":"64"},{"code":"6404","name":"固原市","provinceCode":"64"},{"code":"6405","name":"中卫市","provinceCode":"64"},{"code":"6501","name":"乌鲁木齐市","provinceCode":"65"},{"code":"6502","name":"克拉玛依市","provinceCode":"65"},{"code":"6504","name":"吐鲁番市","provinceCode":"65"},{"code":"6505","name":"哈密市","provinceCode":"65"},{"code":"6523","name":"昌吉回族自治州","provinceCode":"65"},{"code":"6527","name":"博尔塔拉蒙古自治州","provinceCode":"65"},{"code":"6528","name":"巴音郭楞蒙古自治州","provinceCode":"65"},{"code":"6529","name":"阿克苏地区","provinceCode":"65"},{"code":"6530","name":"克孜勒苏柯尔克孜自治州","provinceCode":"65"},{"code":"6531","name":"喀什地区","provinceCode":"65"},{"code":"6532","name":"和田地区","provinceCode":"65"},{"code":"6540","name":"伊犁哈萨克自治州","provinceCode":"65"},{"code":"6542","name":"塔城地区","provinceCode":"65"},{"code":"6543","name":"阿勒泰地区","provinceCode":"65"}]
2 |
--------------------------------------------------------------------------------
/doc/Flutter版携程AppDemo接口文档.md:
--------------------------------------------------------------------------------
1 | - [接口地址](#接口地址)
2 | - [接口字段](#接口字段)
3 | - [HomeModel](#HomeModel)
4 | - [CommonModel](#CommonModel)
5 | - [GridNavModel](#GridNavModel)
6 | - [SalesBoxModel](#SalesBoxModel)
7 | - [ConfigModel](#ConfigModel)
8 |
9 |
10 | ## 接口地址
11 |
12 | [http://www.devio.org/io/flutter_app/json/home_page.json](http://www.devio.org/io/flutter_app/json/home_page.json)
13 |
14 | ## 接口字段
15 |
16 | [JSON在线解析](https://www.json.cn/)
17 |
18 | ## HomeModel
19 |
20 | 
21 |
22 | 字段 | 类型 | 备注
23 | | -------- | -------- | -------- |
24 | config | Object | NonNull
25 | bannerList | Array | NonNull
26 | localNavList | Array | NonNull
27 | gridNav | Object | NonNull
28 | subNavList | Array | NonNull
29 | salesBox | Object | NonNull
30 |
31 | ## CommonModel
32 |
33 | 
34 |
35 | 字段 | 类型 | 备注
36 | | -------- | -------- | -------- |
37 | icon | String | Nullable
38 | title | String | Nullable
39 | url | String | NonNull
40 | statusBarColor | String | Nullable
41 | hideAppBar | bool | Nullable
42 |
43 | ## GridNavModel
44 |
45 | 
46 |
47 | 字段 | 类型 | 备注
48 | | -------- | -------- | -------- |
49 | hotel | Object | NonNull
50 | flight | Object | NonNull
51 | travel | Object | NonNull
52 |
53 | ## SalesBoxModel
54 |
55 | 
56 |
57 | 字段 | 类型 | 备注
58 | | -------- | -------- | -------- |
59 | icon | String | NonNull
60 | moreUrl | String | NonNull
61 | bigCard1 | Object | NonNull
62 | bigCard2 | Object | NonNull
63 | smallCard1 | Object | NonNull
64 | smallCard2 | Object | NonNull
65 | smallCard3 | Object | NonNull
66 | smallCard4 | Object | NonNull
67 |
68 | ## ConfigModel
69 |
70 | 
71 |
72 | 字段 | 类型 | 备注
73 | | -------- | -------- | -------- |
74 | searchUrl | String | NonNull
75 |
76 |
77 | ## 搜索页接口
78 | https://m.ctrip.com/restapi/h5api/searchapp/search?source=mobileweb&action=autocomplete&contentType=json&keyword=1
79 |
80 | ## 旅拍接口
81 | 旅拍类别
82 | [http://www.devio.org/io/flutter_app/json/travel_page.json](http://www.devio.org/io/flutter_app/json/travel_page.json)
83 | 旅拍页地址
84 | https://m.ctrip.com/restapi/soa2/16189/json/searchTripShootListForHomePageV2?_fxpcqlniredt=09031014111431397988&__gw_appid=99999999&__gw_ver=1.0&__gw_from=10650013707&__gw_platform=H5
85 | ```
86 | "params": {
87 | "districtId": -1,
88 | "groupChannelCode": "tourphoto_global1",
89 | "type": null,
90 | "lat": -180,
91 | "lon": -180,
92 | "locatedDistrictId": 2,
93 | "pagePara": {
94 | "pageIndex": 1,
95 | "pageSize": 10,
96 | "sortType": 9,
97 | "sortDirection": 0
98 | },
99 | "imageCutType": 1,
100 | "head": {
101 | "cid": "09031014111431397988",
102 | "ctok": "",
103 | "cver": "1.0",
104 | "lang": "01",
105 | "sid": "8888",
106 | "syscode": "09",
107 | "auth": null,
108 | "extension": [
109 | {
110 | "name": "protocal",
111 | "value": "https"
112 | }
113 | ]
114 | },
115 | "contentType": "json"
116 | }
117 | ```
--------------------------------------------------------------------------------
/images/launch_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/images/launch_image.png
--------------------------------------------------------------------------------
/images/君伟说.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/images/君伟说.png
--------------------------------------------------------------------------------
/ios/.gitignore:
--------------------------------------------------------------------------------
1 | *.mode1v3
2 | *.mode2v3
3 | *.moved-aside
4 | *.pbxuser
5 | *.perspectivev3
6 | **/*sync/
7 | .sconsign.dblite
8 | .tags*
9 | **/.vagrant/
10 | **/DerivedData/
11 | Icon?
12 | **/Pods/
13 | **/.symlinks/
14 | profile
15 | xcuserdata
16 | **/.generated/
17 | Flutter/App.framework
18 | Flutter/Flutter.framework
19 | Flutter/Flutter.podspec
20 | Flutter/Generated.xcconfig
21 | Flutter/app.flx
22 | Flutter/app.zip
23 | Flutter/flutter_assets/
24 | Flutter/flutter_export_environment.sh
25 | ServiceDefinitions.json
26 | Runner/GeneratedPluginRegistrant.*
27 |
28 | # Exceptions to above rules.
29 | !default.mode1v3
30 | !default.mode2v3
31 | !default.pbxuser
32 | !default.perspectivev3
33 |
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 8.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | # platform :ios, '9.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | project 'Runner', {
8 | 'Debug' => :debug,
9 | 'Profile' => :release,
10 | 'Release' => :release,
11 | }
12 |
13 | def parse_KV_file(file, separator='=')
14 | file_abs_path = File.expand_path(file)
15 | if !File.exists? file_abs_path
16 | return [];
17 | end
18 | generated_key_values = {}
19 | skip_line_start_symbols = ["#", "/"]
20 | File.foreach(file_abs_path) do |line|
21 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
22 | plugin = line.split(pattern=separator)
23 | if plugin.length == 2
24 | podname = plugin[0].strip()
25 | path = plugin[1].strip()
26 | podpath = File.expand_path("#{path}", file_abs_path)
27 | generated_key_values[podname] = podpath
28 | else
29 | puts "Invalid plugin specification: #{line}"
30 | end
31 | end
32 | generated_key_values
33 | end
34 |
35 | target 'Runner' do
36 | use_frameworks!
37 | use_modular_headers!
38 |
39 | # Flutter Pod
40 |
41 | copied_flutter_dir = File.join(__dir__, 'Flutter')
42 | copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework')
43 | copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec')
44 | unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path)
45 | # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet.
46 | # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration.
47 | # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist.
48 |
49 | generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig')
50 | unless File.exist?(generated_xcode_build_settings_path)
51 | raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first"
52 | end
53 | generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path)
54 | cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR'];
55 |
56 | unless File.exist?(copied_framework_path)
57 | FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir)
58 | end
59 | unless File.exist?(copied_podspec_path)
60 | FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir)
61 | end
62 | end
63 |
64 | # Keep pod path relative so it can be checked into Podfile.lock.
65 | pod 'Flutter', :path => 'Flutter'
66 |
67 | # Plugin Pods
68 |
69 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
70 | # referring to absolute paths on developers' machines.
71 | system('rm -rf .symlinks')
72 | system('mkdir -p .symlinks/plugins')
73 | plugin_pods = parse_KV_file('../.flutter-plugins')
74 | plugin_pods.each do |name, path|
75 | symlink = File.join('.symlinks', 'plugins', name)
76 | File.symlink(path, symlink)
77 | pod name, :path => File.join(symlink, 'ios')
78 | end
79 | end
80 |
81 | # Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system.
82 | install! 'cocoapods', :disable_input_output_paths => true
83 |
84 | post_install do |installer|
85 | installer.pods_project.targets.each do |target|
86 | target.build_configurations.each do |config|
87 | config.build_settings['ENABLE_BITCODE'] = 'NO'
88 | end
89 | end
90 | end
91 |
--------------------------------------------------------------------------------
/ios/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Flutter (1.0.0)
3 | - flutter_webview_plugin (0.0.1):
4 | - Flutter
5 | - FMDB (2.7.5):
6 | - FMDB/standard (= 2.7.5)
7 | - FMDB/standard (2.7.5)
8 | - package_info (0.0.1):
9 | - Flutter
10 | - path_provider (0.0.1):
11 | - Flutter
12 | - path_provider_macos (0.0.1):
13 | - Flutter
14 | - sqflite (0.0.1):
15 | - Flutter
16 | - FMDB (~> 2.7.2)
17 |
18 | DEPENDENCIES:
19 | - Flutter (from `Flutter`)
20 | - flutter_webview_plugin (from `.symlinks/plugins/flutter_webview_plugin/ios`)
21 | - package_info (from `.symlinks/plugins/package_info/ios`)
22 | - path_provider (from `.symlinks/plugins/path_provider/ios`)
23 | - path_provider_macos (from `.symlinks/plugins/path_provider_macos/ios`)
24 | - sqflite (from `.symlinks/plugins/sqflite/ios`)
25 |
26 | SPEC REPOS:
27 | https://github.com/cocoapods/specs.git:
28 | - FMDB
29 |
30 | EXTERNAL SOURCES:
31 | Flutter:
32 | :path: Flutter
33 | flutter_webview_plugin:
34 | :path: ".symlinks/plugins/flutter_webview_plugin/ios"
35 | package_info:
36 | :path: ".symlinks/plugins/package_info/ios"
37 | path_provider:
38 | :path: ".symlinks/plugins/path_provider/ios"
39 | path_provider_macos:
40 | :path: ".symlinks/plugins/path_provider_macos/ios"
41 | sqflite:
42 | :path: ".symlinks/plugins/sqflite/ios"
43 |
44 | SPEC CHECKSUMS:
45 | Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
46 | flutter_webview_plugin: ed9e8a6a96baf0c867e90e1bce2673913eeac694
47 | FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
48 | package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62
49 | path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
50 | path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0
51 | sqflite: 4001a31ff81d210346b500c55b17f4d6c7589dd0
52 |
53 | PODFILE CHECKSUM: 1b66dae606f75376c5f2135a8290850eeb09ae83
54 |
55 | COCOAPODS: 1.6.1
56 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
54 |
56 |
62 |
63 |
64 |
65 |
66 |
67 |
73 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @UIApplicationMain
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | flutter_wtrip
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | $(FLUTTER_BUILD_NAME)
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(FLUTTER_BUILD_NUMBER)
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UISupportedInterfaceOrientations
30 |
31 | UIInterfaceOrientationPortrait
32 | UIInterfaceOrientationLandscapeLeft
33 | UIInterfaceOrientationLandscapeRight
34 |
35 | UISupportedInterfaceOrientations~ipad
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationPortraitUpsideDown
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 | UIViewControllerBasedStatusBarAppearance
43 |
44 | NSAppTransportSecurity
45 |
46 | NSAllowsArbitraryLoads
47 |
48 | NSAllowsArbitraryLoadsInWebContent
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/lib/dao/home_dao.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_wtrip/model/home_model.dart';
2 | import 'package:http/http.dart' as http;
3 | import 'dart:async'; // 异步编程 Future
4 | import 'dart:convert'; // http.Response 转换
5 | import 'package:dio/dio.dart';
6 | const HOME_URL = 'http://www.devio.org/io/flutter_app/json/home_page.json';
7 | class HomeDao{
8 | static Future fetch() async{
9 | // final response = await http.get(HOME_URL);
10 | // if(response.statusCode == 200) {
11 | // Utf8Decoder utf8decoder = Utf8Decoder(); // fix 中文乱码
12 | // var result = json.decode(utf8decoder.convert(response.bodyBytes));
13 | // return HomeModel.fromJson(result); // 转换成HomeModel
14 | // }else {
15 | // throw Exception('Failed to load home_page json');
16 | // }
17 |
18 | Response response = await Dio().get(HOME_URL);
19 | if(response.statusCode == 200) {
20 | return HomeModel.fromJson(response.data); // 转换成HomeModel
21 | } else {
22 | throw Exception('Failed to load home_page json');
23 | }
24 |
25 | }
26 | }
--------------------------------------------------------------------------------
/lib/dao/search_dao.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_wtrip/model/search_model.dart';
2 | import 'package:http/http.dart' as http;
3 | import 'dart:async'; // 异步编程 Future
4 | import 'dart:convert'; // http.Response 转换
5 | import 'package:dio/dio.dart';
6 |
7 | const SEARCH_URL =
8 | 'https://m.ctrip.com/restapi/h5api/searchapp/search?source=mobileweb&action=autocomplete&contentType=json&keyword=';
9 |
10 | class SearchDao {
11 | static Future fetch(String text) async {
12 | Response response = await Dio().get(SEARCH_URL+text);
13 | print(response.toString());
14 | if (response.statusCode == 200) {
15 | // Utf8Decoder utf8decoder = Utf8Decoder(); // fix 中文乱码
16 | // var result = json.decode(utf8decoder.convert(response.bodyBytes));
17 | // 只有当当前输入的内容和服务器返回的内容一致时才渲染
18 | SearchModel model = SearchModel.fromJson(response.data);
19 | model.keyword = text;
20 | return model; // 转换成HomeModel
21 | } else {
22 | throw Exception('Failed to load search_page json');
23 | }
24 |
25 |
26 |
27 |
28 | // final response = await http.get(SEARCH_URL + text);
29 | // if (response.statusCode == 200) {
30 | // Utf8Decoder utf8decoder = Utf8Decoder(); // fix 中文乱码
31 | // var result = json.decode(utf8decoder.convert(response.bodyBytes));
32 | // // 只有当当前输入的内容和服务器返回的内容一致时才渲染
33 | // SearchModel model = SearchModel.fromJson(result);
34 | // model.keyword = text;
35 | // return model; // 转换成HomeModel
36 | // } else {
37 | // throw Exception('Failed to load search_page json');
38 | // }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/lib/dao/travel_page_dao.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_wtrip/model/travel_page_model.dart';
2 | import 'package:http/http.dart' as http;
3 | import 'dart:async'; // 异步编程 Future
4 | import 'dart:convert'; // http.Response 转换
5 | import 'package:dio/dio.dart';
6 |
7 | const TRAVEL_PAGE_DEFAULT_URL =
8 | 'https://m.ctrip.com/restapi/soa2/16189/json/searchTripShootListForHomePageV2?_fxpcqlniredt=09031014111431397988&__gw_appid=99999999&__gw_ver=1.0&__gw_from=10650013707&__gw_platform=H5';
9 | // 固定参数,如果要被修改,不要定义成const
10 | var Params = {
11 | "districtId": -1,
12 | "groupChannelCode": "tourphoto_global1",
13 | "type": null,
14 | "lat": -180,
15 | "lon": -180,
16 | "locatedDistrictId": 2,
17 | "pagePara": {
18 | "pageIndex": 1,
19 | "pageSize": 10,
20 | "sortType": 9,
21 | "sortDirection": 0
22 | },
23 | "imageCutType": 1,
24 | "head": {},
25 | "contentType": "json"
26 | };
27 |
28 | class TravelPageDao {
29 | static Future fetch(
30 | String url, String groupChannelCode, int pageIndex, int pageSize) async {
31 | // 可变参数
32 | Map paramsMap = Params['pagePara'];
33 | paramsMap['pageIndex'] = pageIndex;
34 | paramsMap['pageSize'] = pageSize;
35 | Params['groupChannelCode'] = groupChannelCode;
36 | // var response = await http.post(TRAVEL_PAGE_DEFAULT_URL, body: jsonEncode(Params));
37 | // if (response.statusCode == 200) {
38 | // Utf8Decoder utf8decoder = Utf8Decoder(); // fix 中文乱码
39 | // var result = json.decode(utf8decoder.convert(response.bodyBytes));
40 | // return TravelPageModel.fromJson(result); // 转换成HomeModel
41 | // } else {
42 | // throw Exception('Failed to load travel page json');
43 | // }
44 | Response response = await Dio().post(TRAVEL_PAGE_DEFAULT_URL, data: Params);
45 | if(response.statusCode == 200) {
46 | return TravelPageModel.fromJson(response.data);
47 | }else{
48 | throw Exception('Failed to load travel page json');
49 | }
50 |
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/lib/dao/travel_tab_dao.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_wtrip/model/travel_tab_model.dart';
2 | import 'package:http/http.dart' as http;
3 | import 'dart:async'; // 异步编程 Future
4 | import 'dart:convert'; // http.Response 转换
5 | import 'package:dio/dio.dart';
6 |
7 | const TRAVEL_TAB_URL =
8 | 'http://www.devio.org/io/flutter_app/json/travel_page.json';
9 |
10 | class TravelTabDao {
11 | static Future fetch() async {
12 | final response = await http.get(TRAVEL_TAB_URL);
13 | if (response.statusCode == 200) {
14 | Utf8Decoder utf8decoder = Utf8Decoder(); // fix 中文乱码
15 | var result = json.decode(utf8decoder.convert(response.bodyBytes));
16 | return TravelTabModel.fromJson(result); // 转换成HomeModel
17 | } else {
18 | throw Exception('Failed to load home_page json');
19 | }
20 |
21 | // Response response = await Dio().get(TRAVEL_TAB_URL);
22 | // if(response.statusCode == 200) {
23 | // return TravelTabModel.fromJson(response.data);
24 | // } else {
25 | // throw Exception('Failed to load TravelTabDao json');
26 | // }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import 'navigator/tab_navigator.dart';
4 |
5 | import 'package:provider/provider.dart';
6 |
7 | void main() => runApp(MyApp());
8 |
9 | class MyApp extends StatelessWidget {
10 | // This widget is the root of your application.
11 | @override
12 | Widget build(BuildContext context) {
13 | return MaterialApp(
14 | title: 'Flutter Demo',
15 | theme: ThemeData(
16 | // This is the theme of your application.
17 | //
18 | // Try running your application with "flutter run". You'll see the
19 | // application has a blue toolbar. Then, without quitting the app, try
20 | // changing the primarySwatch below to Colors.green and then invoke
21 | // "hot reload" (press "r" in the console where you ran "flutter run",
22 | // or simply save your changes to "hot reload" in a Flutter IDE).
23 | // Notice that the counter didn't reset back to zero; the application
24 | // is not restarted.
25 | primarySwatch: Colors.blue,
26 | ),
27 | home: TabNavigator(),
28 | );
29 | }
30 | }
--------------------------------------------------------------------------------
/lib/model/common_model.dart:
--------------------------------------------------------------------------------
1 | class CommonModel {
2 | final String icon;
3 | final String title;
4 | final String url;
5 | final String statusBarColor;
6 | final bool hideAppBar;
7 |
8 | CommonModel(
9 | {this.icon, this.title, this.url, this.statusBarColor, this.hideAppBar});
10 |
11 | factory CommonModel.fromJson(Map json) {
12 | return CommonModel(
13 | icon: json['icon'],
14 | title: json['title'],
15 | url: json['url'],
16 | statusBarColor: json['statusBarColor'],
17 | hideAppBar: json['hideAppBar'],
18 | );
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/lib/model/config_model.dart:
--------------------------------------------------------------------------------
1 | class ConfigModel{
2 | final String searchUrl;
3 |
4 | ConfigModel({this.searchUrl});
5 |
6 | factory ConfigModel.fromJson(Map json) {
7 | return ConfigModel(
8 | searchUrl: json['searchUrl']
9 | );
10 | }
11 |
12 | Map toJson() {
13 | return {searchUrl: searchUrl};
14 | }
15 | }
--------------------------------------------------------------------------------
/lib/model/gridNav_model.dart:
--------------------------------------------------------------------------------
1 | import 'common_model.dart';
2 |
3 | class GridNavModel {
4 | final GridNavItem hotel;
5 | final GridNavItem flight;
6 | final GridNavItem travel;
7 |
8 | GridNavModel({this.hotel, this.flight, this.travel});
9 |
10 | factory GridNavModel.fromJson(Map json) {
11 | return json !=null? GridNavModel(
12 | hotel: GridNavItem.fromJson(json['hotel']),
13 | flight: GridNavItem.fromJson(json['flight']),
14 | travel: GridNavItem.fromJson(json['travel']),
15 | ) : null;
16 | }
17 | }
18 |
19 | class GridNavItem {
20 | final String startColor;
21 | final String endColor;
22 | final CommonModel mainItem;
23 | final CommonModel item1;
24 | final CommonModel item2;
25 | final CommonModel item3;
26 | final CommonModel item4;
27 |
28 | GridNavItem(
29 | {this.startColor,
30 | this.endColor,
31 | this.mainItem,
32 | this.item1,
33 | this.item2,
34 | this.item3,
35 | this.item4});
36 |
37 | factory GridNavItem.fromJson(Map json) {
38 | return GridNavItem(
39 | startColor: json['startColor'],
40 | endColor: json['endColor'],
41 | mainItem: CommonModel.fromJson(json['mainItem']),
42 | item1: CommonModel.fromJson(json['item1']),
43 | item2: CommonModel.fromJson(json['item2']),
44 | item3: CommonModel.fromJson(json['item3']),
45 | item4: CommonModel.fromJson(json['item4']),
46 | );
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/lib/model/home_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_wtrip/model/sales_box_model.dart';
2 |
3 | import 'common_model.dart';
4 | import 'config_model.dart';
5 | import 'gridNav_model.dart';
6 |
7 | class HomeModel {
8 | final ConfigModel config;
9 | final List bannerList;
10 | final List localNavList;
11 | final List subNavList;
12 | final GridNavModel gridNav;
13 | final SalesBoxModel salesBox;
14 |
15 | HomeModel(
16 | {this.config,
17 | this.bannerList,
18 | this.localNavList,
19 | this.subNavList,
20 | this.gridNav,
21 | this.salesBox});
22 |
23 |
24 | factory HomeModel.fromJson(Map json) {
25 | var localNavListJson = json['localNavList'] as List;
26 | List localNavList = localNavListJson.map((i)=> CommonModel.fromJson(i)).toList();
27 |
28 | var bannerListJson = json['bannerList'] as List;
29 | List bannerList = bannerListJson.map((i)=> CommonModel.fromJson(i)).toList();
30 |
31 | var subNavListJson = json['subNavList'] as List;
32 | List subNavList = subNavListJson.map((i)=> CommonModel.fromJson(i)).toList();
33 |
34 |
35 | return HomeModel(
36 | localNavList: localNavList,
37 | bannerList: bannerList,
38 | subNavList: subNavList,
39 | config: ConfigModel.fromJson(json['config']),
40 | gridNav: GridNavModel.fromJson(json['gridNav']),
41 | salesBox: SalesBoxModel.fromJson(json['salesBox']),
42 | );
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/lib/model/sales_box_model.dart:
--------------------------------------------------------------------------------
1 | import 'common_model.dart';
2 |
3 | class SalesBoxModel {
4 | final String icon;
5 | final String moreUrl;
6 | final CommonModel bigCard1;
7 | final CommonModel bigCard2;
8 | final CommonModel smallCard1;
9 | final CommonModel smallCard2;
10 | final CommonModel smallCard3;
11 | final CommonModel smallCard4;
12 |
13 | SalesBoxModel({
14 | this.icon,
15 | this.moreUrl,
16 | this.bigCard1,
17 | this.bigCard2,
18 | this.smallCard1,
19 | this.smallCard2,
20 | this.smallCard3,
21 | this.smallCard4,
22 | });
23 |
24 | factory SalesBoxModel.fromJson(Mapjson) {
25 | return json !=null?
26 | SalesBoxModel(
27 | icon: json['icon'],
28 | moreUrl: json['moreUrl'],
29 | bigCard1: CommonModel.fromJson(json['bigCard1']),
30 | bigCard2: CommonModel.fromJson(json['bigCard2']),
31 | smallCard1: CommonModel.fromJson(json['smallCard1']),
32 | smallCard2: CommonModel.fromJson(json['smallCard2']),
33 | smallCard3: CommonModel.fromJson(json['smallCard3']),
34 | smallCard4: CommonModel.fromJson(json['smallCard4']),
35 | ): null;
36 | }
37 |
38 |
39 | }
--------------------------------------------------------------------------------
/lib/model/search_model.dart:
--------------------------------------------------------------------------------
1 |
2 | ///搜索模型
3 | class SearchModel {
4 | final List data;
5 | String keyword;
6 |
7 | SearchModel({this.data});
8 |
9 | factory SearchModel.fromJson(Map json) {
10 | var dataJson = json['data'] as List;
11 | List data = dataJson.map((i) => SearchItem.fromJson(i)).toList();
12 |
13 | return SearchModel(data: data);
14 | }
15 | }
16 |
17 | class SearchItem {
18 | final String word;
19 | final String type;
20 | final String price;
21 | final String star;
22 | final String zonename;
23 | final String districtname;
24 | final String url;
25 |
26 | SearchItem(
27 | {this.word,
28 | this.type,
29 | this.price,
30 | this.star,
31 | this.zonename,
32 | this.districtname,
33 | this.url});
34 |
35 | factory SearchItem.fromJson(Map json) {
36 | return SearchItem(
37 | word: json['word'],
38 | type: json['type'],
39 | price: json['price'],
40 | zonename: json['zonename'],
41 | districtname: json['districtname'],
42 | url: json['url'],
43 | star: json['star'],
44 | );
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/lib/model/travel_page_model.dart:
--------------------------------------------------------------------------------
1 | ///旅拍页modal
2 | class TravelPageModel {
3 | int totalCount;
4 | List resultList;
5 |
6 | TravelPageModel({this.totalCount, this.resultList});
7 |
8 | TravelPageModel.fromJson(Map json) {
9 | totalCount = json['totalCount'];
10 | if (json['resultList'] != null) {
11 | resultList = new List();
12 | json['resultList'].forEach((v) {
13 | resultList.add(new TravelPageItem.fromJson(v));
14 | });
15 | }
16 | }
17 |
18 | Map toJson() {
19 | final Map data = new Map();
20 | data['totalCount'] = this.totalCount;
21 | if (this.resultList != null) {
22 | data['resultList'] = this.resultList.map((v) => v.toJson()).toList();
23 | }
24 | return data;
25 | }
26 | }
27 |
28 | class TravelPageItem {
29 | int type;
30 | Article article;
31 |
32 | TravelPageItem({this.type, this.article});
33 |
34 | TravelPageItem.fromJson(Map json) {
35 | type = json['type'];
36 | article =
37 | json['article'] != null ? new Article.fromJson(json['article']) : null;
38 | }
39 |
40 | Map toJson() {
41 | final Map data = new Map();
42 | data['type'] = this.type;
43 | if (this.article != null) {
44 | data['article'] = this.article.toJson();
45 | }
46 | return data;
47 | }
48 | }
49 |
50 | class Article {
51 | int articleId;
52 | int productType;
53 | int sourceType;
54 | String articleTitle;
55 | Author author;
56 | List images;
57 | bool hasVideo;
58 | int readCount;
59 | int likeCount;
60 | int commentCount;
61 | int shareCount;
62 | List urls;
63 | List topics;
64 | List pois;
65 | String publishTime;
66 | String publishTimeDisplay;
67 | String shootTime;
68 | String shootTimeDisplay;
69 | int level;
70 | String distanceText;
71 | bool isLike;
72 | int imageCounts;
73 | bool isCollected;
74 | int collectCount;
75 | int articleStatus;
76 | String poiName;
77 | ShareInfo shareInfo;
78 | String currentDate;
79 | int sourceId;
80 |
81 | Article(
82 | {this.articleId,
83 | this.productType,
84 | this.sourceType,
85 | this.articleTitle,
86 | this.author,
87 | this.images,
88 | this.hasVideo,
89 | this.readCount,
90 | this.likeCount,
91 | this.commentCount,
92 | this.shareCount,
93 | this.urls,
94 | this.topics,
95 | this.pois,
96 | this.publishTime,
97 | this.publishTimeDisplay,
98 | this.shootTime,
99 | this.shootTimeDisplay,
100 | this.level,
101 | this.distanceText,
102 | this.isLike,
103 | this.imageCounts,
104 | this.isCollected,
105 | this.collectCount,
106 | this.articleStatus,
107 | this.poiName,
108 | this.shareInfo,
109 | this.currentDate,
110 | this.sourceId});
111 |
112 | Article.fromJson(Map json) {
113 | articleId = json['articleId'];
114 | productType = json['productType'];
115 | sourceType = json['sourceType'];
116 | articleTitle = json['articleTitle'];
117 | author =
118 | json['author'] != null ? new Author.fromJson(json['author']) : null;
119 | if (json['images'] != null) {
120 | images = new List();
121 | json['images'].forEach((v) {
122 | images.add(new Images.fromJson(v));
123 | });
124 | }
125 | hasVideo = json['hasVideo'];
126 | readCount = json['readCount'];
127 | likeCount = json['likeCount'];
128 | commentCount = json['commentCount'];
129 | shareCount = json['shareCount'];
130 | if (json['urls'] != null) {
131 | urls = new List();
132 | json['urls'].forEach((v) {
133 | urls.add(new Urls.fromJson(v));
134 | });
135 | }
136 | if (json['topics'] != null) {
137 | topics = new List();
138 | json['topics'].forEach((v) {
139 | topics.add(new Topics.fromJson(v));
140 | });
141 | }
142 | if (json['pois'] != null) {
143 | pois = new List();
144 | json['pois'].forEach((v) {
145 | pois.add(new Pois.fromJson(v));
146 | });
147 | }
148 | publishTime = json['publishTime'];
149 | publishTimeDisplay = json['publishTimeDisplay'];
150 | shootTime = json['shootTime'];
151 | shootTimeDisplay = json['shootTimeDisplay'];
152 | level = json['level'];
153 | distanceText = json['distanceText'];
154 | isLike = json['isLike'];
155 | imageCounts = json['imageCounts'];
156 | isCollected = json['isCollected'];
157 | collectCount = json['collectCount'];
158 | articleStatus = json['articleStatus'];
159 | poiName = json['poiName'];
160 | shareInfo = json['shareInfo'] != null
161 | ? new ShareInfo.fromJson(json['shareInfo'])
162 | : null;
163 | currentDate = json['currentDate'];
164 | sourceId = json['sourceId'];
165 | }
166 |
167 | Map toJson() {
168 | final Map data = new Map();
169 | data['articleId'] = this.articleId;
170 | data['productType'] = this.productType;
171 | data['sourceType'] = this.sourceType;
172 | data['articleTitle'] = this.articleTitle;
173 | if (this.author != null) {
174 | data['author'] = this.author.toJson();
175 | }
176 | if (this.images != null) {
177 | data['images'] = this.images.map((v) => v.toJson()).toList();
178 | }
179 | data['hasVideo'] = this.hasVideo;
180 | data['readCount'] = this.readCount;
181 | data['likeCount'] = this.likeCount;
182 | data['commentCount'] = this.commentCount;
183 | data['shareCount'] = this.shareCount;
184 | if (this.urls != null) {
185 | data['urls'] = this.urls.map((v) => v.toJson()).toList();
186 | }
187 | if (this.topics != null) {
188 | data['topics'] = this.topics.map((v) => v.toJson()).toList();
189 | }
190 | if (this.pois != null) {
191 | data['pois'] = this.pois.map((v) => v.toJson()).toList();
192 | }
193 | data['publishTime'] = this.publishTime;
194 | data['publishTimeDisplay'] = this.publishTimeDisplay;
195 | data['shootTime'] = this.shootTime;
196 | data['shootTimeDisplay'] = this.shootTimeDisplay;
197 | data['level'] = this.level;
198 | data['distanceText'] = this.distanceText;
199 | data['isLike'] = this.isLike;
200 | data['imageCounts'] = this.imageCounts;
201 | data['isCollected'] = this.isCollected;
202 | data['collectCount'] = this.collectCount;
203 | data['articleStatus'] = this.articleStatus;
204 | data['poiName'] = this.poiName;
205 | if (this.shareInfo != null) {
206 | data['shareInfo'] = this.shareInfo.toJson();
207 | }
208 | data['currentDate'] = this.currentDate;
209 | data['sourceId'] = this.sourceId;
210 | return data;
211 | }
212 | }
213 |
214 | class Author {
215 | int authorId;
216 | String nickName;
217 | String clientAuth;
218 | String jumpUrl;
219 | CoverImage coverImage;
220 | int identityType;
221 | String identityTypeName;
222 | String tag;
223 | int followCount;
224 |
225 | Author(
226 | {this.authorId,
227 | this.nickName,
228 | this.clientAuth,
229 | this.jumpUrl,
230 | this.coverImage,
231 | this.identityType,
232 | this.identityTypeName,
233 | this.tag,
234 | this.followCount});
235 |
236 | Author.fromJson(Map json) {
237 | authorId = json['authorId'];
238 | nickName = json['nickName'];
239 | clientAuth = json['clientAuth'];
240 | jumpUrl = json['jumpUrl'];
241 | coverImage = json['coverImage'] != null
242 | ? new CoverImage.fromJson(json['coverImage'])
243 | : null;
244 | identityType = json['identityType'];
245 | identityTypeName = json['identityTypeName'];
246 | tag = json['tag'];
247 | followCount = json['followCount'];
248 | }
249 |
250 | Map toJson() {
251 | final Map data = new Map();
252 | data['authorId'] = this.authorId;
253 | data['nickName'] = this.nickName;
254 | data['clientAuth'] = this.clientAuth;
255 | data['jumpUrl'] = this.jumpUrl;
256 | if (this.coverImage != null) {
257 | data['coverImage'] = this.coverImage.toJson();
258 | }
259 | data['identityType'] = this.identityType;
260 | data['identityTypeName'] = this.identityTypeName;
261 | data['tag'] = this.tag;
262 | data['followCount'] = this.followCount;
263 | return data;
264 | }
265 | }
266 |
267 | class CoverImage {
268 | String dynamicUrl;
269 | String originalUrl;
270 |
271 | CoverImage({this.dynamicUrl, this.originalUrl});
272 |
273 | CoverImage.fromJson(Map json) {
274 | dynamicUrl = json['dynamicUrl'];
275 | originalUrl = json['originalUrl'];
276 | }
277 |
278 | Map toJson() {
279 | final Map data = new Map();
280 | data['dynamicUrl'] = this.dynamicUrl;
281 | data['originalUrl'] = this.originalUrl;
282 | return data;
283 | }
284 | }
285 |
286 | class Images {
287 | int imageId;
288 | String dynamicUrl;
289 | String originalUrl;
290 | double width;
291 | double height;
292 | int mediaType;
293 | double lat;
294 | double lon;
295 | bool isWaterMarked;
296 |
297 | Images(
298 | {this.imageId,
299 | this.dynamicUrl,
300 | this.originalUrl,
301 | this.width,
302 | this.height,
303 | this.mediaType,
304 | this.lat,
305 | this.lon,
306 | this.isWaterMarked});
307 |
308 | Images.fromJson(Map json) {
309 | imageId = json['imageId'];
310 | dynamicUrl = json['dynamicUrl'];
311 | originalUrl = json['originalUrl'];
312 | width = json['width'];
313 | height = json['height'];
314 | mediaType = json['mediaType'];
315 | lat = json['lat'];
316 | lon = json['lon'];
317 | isWaterMarked = json['isWaterMarked'];
318 | }
319 |
320 | Map toJson() {
321 | final Map data = new Map();
322 | data['imageId'] = this.imageId;
323 | data['dynamicUrl'] = this.dynamicUrl;
324 | data['originalUrl'] = this.originalUrl;
325 | data['width'] = this.width;
326 | data['height'] = this.height;
327 | data['mediaType'] = this.mediaType;
328 | data['lat'] = this.lat;
329 | data['lon'] = this.lon;
330 | data['isWaterMarked'] = this.isWaterMarked;
331 | return data;
332 | }
333 | }
334 |
335 | class Urls {
336 | String version;
337 | String appUrl;
338 | String h5Url;
339 | String wxUrl;
340 |
341 | Urls({this.version, this.appUrl, this.h5Url, this.wxUrl});
342 |
343 | Urls.fromJson(Map json) {
344 | version = json['version'];
345 | appUrl = json['appUrl'];
346 | h5Url = json['h5Url'];
347 | wxUrl = json['wxUrl'];
348 | }
349 |
350 | Map toJson() {
351 | final Map data = new Map();
352 | data['version'] = this.version;
353 | data['appUrl'] = this.appUrl;
354 | data['h5Url'] = this.h5Url;
355 | data['wxUrl'] = this.wxUrl;
356 | return data;
357 | }
358 | }
359 |
360 | class Topics {
361 | int topicId;
362 | String topicName;
363 | int level;
364 |
365 | Topics({this.topicId, this.topicName, this.level});
366 |
367 | Topics.fromJson(Map json) {
368 | topicId = json['topicId'];
369 | topicName = json['topicName'];
370 | level = json['level'];
371 | }
372 |
373 | Map toJson() {
374 | final Map data = new Map();
375 | data['topicId'] = this.topicId;
376 | data['topicName'] = this.topicName;
377 | data['level'] = this.level;
378 | return data;
379 | }
380 | }
381 |
382 | class Pois {
383 | int poiType;
384 | int poiId;
385 | String poiName;
386 | int businessId;
387 | int districtId;
388 | PoiExt poiExt;
389 | int source;
390 | int isMain;
391 | bool isInChina;
392 | String countryName;
393 |
394 | Pois(
395 | {this.poiType,
396 | this.poiId,
397 | this.poiName,
398 | this.businessId,
399 | this.districtId,
400 | this.poiExt,
401 | this.source,
402 | this.isMain,
403 | this.isInChina,
404 | this.countryName});
405 |
406 | Pois.fromJson(Map json) {
407 | poiType = json['poiType'];
408 | poiId = json['poiId'];
409 | poiName = json['poiName'];
410 | businessId = json['businessId'];
411 | districtId = json['districtId'];
412 | poiExt =
413 | json['poiExt'] != null ? new PoiExt.fromJson(json['poiExt']) : null;
414 | source = json['source'];
415 | isMain = json['isMain'];
416 | isInChina = json['isInChina'];
417 | countryName = json['countryName'];
418 | }
419 |
420 | Map toJson() {
421 | final Map data = new Map();
422 | data['poiType'] = this.poiType;
423 | data['poiId'] = this.poiId;
424 | data['poiName'] = this.poiName;
425 | data['businessId'] = this.businessId;
426 | data['districtId'] = this.districtId;
427 | if (this.poiExt != null) {
428 | data['poiExt'] = this.poiExt.toJson();
429 | }
430 | data['source'] = this.source;
431 | data['isMain'] = this.isMain;
432 | data['isInChina'] = this.isInChina;
433 | data['countryName'] = this.countryName;
434 | return data;
435 | }
436 | }
437 |
438 | class PoiExt {
439 | String h5Url;
440 | String appUrl;
441 |
442 | PoiExt({this.h5Url, this.appUrl});
443 |
444 | PoiExt.fromJson(Map json) {
445 | h5Url = json['h5Url'];
446 | appUrl = json['appUrl'];
447 | }
448 |
449 | Map toJson() {
450 | final Map data = new Map();
451 | data['h5Url'] = this.h5Url;
452 | data['appUrl'] = this.appUrl;
453 | return data;
454 | }
455 | }
456 |
457 | class ShareInfo {
458 | String imageUrl;
459 | String shareTitle;
460 | String shareContent;
461 | String platForm;
462 | String token;
463 |
464 | ShareInfo(
465 | {this.imageUrl,
466 | this.shareTitle,
467 | this.shareContent,
468 | this.platForm,
469 | this.token});
470 |
471 | ShareInfo.fromJson(Map json) {
472 | imageUrl = json['imageUrl'];
473 | shareTitle = json['shareTitle'];
474 | shareContent = json['shareContent'];
475 | platForm = json['platForm'];
476 | token = json['token'];
477 | }
478 |
479 | Map toJson() {
480 | final Map data = new Map();
481 | data['imageUrl'] = this.imageUrl;
482 | data['shareTitle'] = this.shareTitle;
483 | data['shareContent'] = this.shareContent;
484 | data['platForm'] = this.platForm;
485 | data['token'] = this.token;
486 | return data;
487 | }
488 | }
--------------------------------------------------------------------------------
/lib/model/travel_tab_model.dart:
--------------------------------------------------------------------------------
1 |
2 | /// 旅拍模块顶部tab model
3 | class TravelTabModel {
4 | String url;
5 | List tabs;
6 |
7 | TravelTabModel({this.url, this.tabs});
8 |
9 | TravelTabModel.fromJson(Map json) {
10 | url = json['url'];
11 | if (json['tabs'] != null) {
12 | tabs = new List();
13 | json['tabs'].forEach((v) {
14 | tabs.add(new TravelTab.fromJson(v));
15 | });
16 | }
17 | }
18 |
19 | Map toJson() {
20 | final Map data = new Map();
21 | data['url'] = this.url;
22 | if (this.tabs != null) {
23 | data['tabs'] = this.tabs.map((v) => v.toJson()).toList();
24 | }
25 | return data;
26 | }
27 | }
28 |
29 |
30 | class TravelTab {
31 | String labelName;
32 | String groupChannelCode;
33 |
34 | TravelTab({this.labelName, this.groupChannelCode});
35 |
36 | TravelTab.fromJson(Map json) {
37 | labelName = json['labelName'];
38 | groupChannelCode = json['groupChannelCode'];
39 | }
40 |
41 | Map toJson() {
42 | final Map data = new Map();
43 | data['labelName'] = this.labelName;
44 | data['groupChannelCode'] = this.groupChannelCode;
45 | return data;
46 | }
47 | }
--------------------------------------------------------------------------------
/lib/navigator/tab_navigator.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_wtrip/pages/home_page.dart';
3 | import 'package:flutter_wtrip/pages/my_page.dart';
4 | import 'package:flutter_wtrip/pages/search_page.dart';
5 | import 'package:flutter_wtrip/pages/travel_page.dart';
6 |
7 | class TabNavigator extends StatefulWidget {
8 | @override
9 | _TabNavigatorState createState() => _TabNavigatorState();
10 | }
11 |
12 | class _TabNavigatorState extends State {
13 | final _defaultColor = Colors.grey;
14 | final _activeColor = Colors.blue;
15 | int _currentIndex = 0;
16 |
17 | final PageController _controller = PageController(
18 | initialPage: 0,
19 | );
20 |
21 | void _onItemTapped(int value) {
22 | _controller.jumpToPage(value);
23 | setState(() {
24 | _currentIndex = value;
25 | });
26 | }
27 |
28 | @override
29 | Widget build(BuildContext context) {
30 | // TODO: implement build
31 | return Scaffold(
32 | body: PageView(
33 | physics: NeverScrollableScrollPhysics(), // 禁止导航页滑动
34 | controller: _controller,
35 | children: [
36 | HomePage(),
37 | SearchPage(hideLeft: true,),
38 | TravelPage(),
39 | MyPage(),
40 | ],
41 | ),
42 | bottomNavigationBar: BottomNavigationBar(
43 | items: [
44 | _tabBarItem(Icons.home, '首页', 0),
45 | _tabBarItem(Icons.search, '搜索', 1),
46 | _tabBarItem(Icons.camera_alt, '旅拍', 2),
47 | _tabBarItem(Icons.account_circle, "我的", 3),
48 | ],
49 | currentIndex: _currentIndex,
50 | // selectedItemColor: _activeColor,
51 | onTap: _onItemTapped,
52 | type: BottomNavigationBarType.fixed, // 固定显示
53 | ),
54 | );
55 | }
56 |
57 | _tabBarItem(IconData icon, String title, int index) {
58 | return BottomNavigationBarItem(
59 | icon: Icon(
60 | icon,
61 | color: _defaultColor,
62 | ),
63 | activeIcon: Icon(
64 | icon,
65 | color: _activeColor,
66 | ),
67 | title: Text(title,
68 | style: TextStyle(
69 | color:
70 | _currentIndex != index ? _defaultColor : _activeColor)));
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/lib/pages/city_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter/services.dart';
4 | import 'package:flutter_wtrip/widgets/loading_container.dart';
5 | import 'package:azlistview/azlistview.dart';
6 | import 'package:lpinyin/lpinyin.dart';
7 |
8 | class CityPage extends StatefulWidget {
9 | @override
10 | _CityPageState createState() => _CityPageState();
11 | }
12 |
13 | class _CityPageState extends State {
14 |
15 | List _cityList = List();
16 | int _suspensionHeight = 40;
17 | int _itemHeight = 50;
18 | String _suspensionTag = "";
19 |
20 |
21 | @override
22 | void initState() {
23 | // TODO: implement initState
24 | _loadCityData();
25 | super.initState();
26 | }
27 |
28 | void _loadCityData() async {
29 | try {
30 | var value = await rootBundle.loadString('assets/data/cities.json');
31 | List list = json.decode(value);
32 | list.forEach((value){
33 | _cityList.add(CityInfo(name: value['name']));
34 | });
35 | _handleList(_cityList);
36 | setState(() {
37 | _cityList = _cityList;
38 | });
39 |
40 | // print(_cityList);
41 | } catch (e) {
42 | print(e);
43 | }
44 | }
45 |
46 | //排序
47 | void _handleList(List list) {
48 | if (list == null || list.isEmpty) return;
49 | for (int i = 0, length = list.length; i < length; i++) {
50 | String pinyin = PinyinHelper.getPinyinE(list[i].name);
51 | String tag = pinyin.substring(0, 1).toUpperCase();
52 | list[i].namePinyin = pinyin;
53 | if (RegExp("[A-Z]").hasMatch(tag)) {
54 | list[i].tagIndex = tag;
55 | } else {
56 | list[i].tagIndex = "#";
57 | }
58 | }
59 | //根据A-Z排序
60 | SuspensionUtil.sortListBySuspensionTag(_cityList);
61 | }
62 |
63 | //tag更改
64 | void _onSusTagChanged(String tag) {
65 | setState(() {
66 | _suspensionTag = tag;
67 | });
68 | }
69 |
70 | @override
71 | Widget build(BuildContext context) {
72 | // TODO: implement build
73 | return MaterialApp(
74 | theme: ThemeData(
75 | primarySwatch: Colors.blue
76 | ),
77 | home: Scaffold(
78 | appBar: AppBar(
79 | title: Text('选择城市'),
80 | leading: GestureDetector(
81 | onTap: (){
82 | Navigator.pop(context);
83 | },
84 | child: Icon(Icons.arrow_back),
85 | ),
86 | ),
87 | body: Column(
88 | children: [
89 | ListTile(
90 | title: Text('定位城市'),
91 | trailing: Row(
92 | mainAxisSize: MainAxisSize.min,
93 | children: [
94 | Icon(Icons.place, size: 20.0,),
95 | Text('北京市')
96 | ],
97 | ),
98 | ),
99 | Divider(height: .0,),
100 | Expanded(
101 | flex: 1,
102 | child: AzListView(
103 | data: _cityList,
104 | itemBuilder: (context, model) => _buildListItem(model),
105 | suspensionWidget: _buildSusWidget(_suspensionTag),
106 | isUseRealIndex: true,
107 | itemHeight: _itemHeight,
108 | onSusTagChanged: _onSusTagChanged,
109 | header: AzListViewHeader(
110 | tag: '*',
111 | height: 140,
112 | builder: (context){
113 | return _buildHeader();
114 | }),
115 | indexHintBuilder: (context,hint){
116 | return Container(
117 | alignment: Alignment.center,
118 | width: 80.0,
119 | height: 80.0,
120 | decoration: BoxDecoration(
121 | color: Colors.black54, shape: BoxShape.circle
122 | ),
123 | child: Text(hint,
124 | style: TextStyle(color: Colors.white, fontSize: 30.0)),
125 | );
126 | },
127 | ),
128 | )
129 | ],
130 | ),
131 | ),
132 | );
133 | }
134 |
135 | Widget _buildHeader(){
136 | List hotCityList = List();
137 | hotCityList.addAll([
138 | CityInfo(name: "北京市"),
139 | CityInfo(name: "上海市"),
140 | CityInfo(name: "广州市"),
141 | CityInfo(name: "深圳市"),
142 | CityInfo(name: "杭州市"),
143 | CityInfo(name: "成都市"),
144 | ]);
145 | return Padding(
146 | padding: const EdgeInsets.symmetric(horizontal: 16.0),
147 | child: Wrap(
148 | alignment: WrapAlignment.center,
149 | runAlignment: WrapAlignment.center,
150 | spacing: 10.0,
151 | children: hotCityList.map((e) {
152 | return OutlineButton(
153 | borderSide: BorderSide(color: Colors.grey[300], width: .5),
154 | child: Text(e.name),
155 | onPressed: () {
156 | print("OnItemClick: $e");
157 | Navigator.pop(context, e.name);
158 | },
159 | );
160 | }).toList(),
161 | ),
162 | );
163 | }
164 |
165 | Widget _buildSusWidget(String susTag){
166 | return Container(
167 | height: _suspensionHeight.toDouble(),
168 | padding: const EdgeInsets.only(left: 15.0),
169 | color: Color(0xfff3f4f5),
170 | alignment: Alignment.centerLeft,
171 | child: Text(
172 | '$susTag',
173 | softWrap: false,
174 | style: TextStyle(
175 | fontSize: 14.0,
176 | color: Color(0xff999999)
177 | ),
178 | ),
179 | );
180 | }
181 |
182 | // 自定义item
183 | Widget _buildListItem(CityInfo model){
184 | String susTag = model.getSuspensionTag();
185 | return Column(
186 | children: [
187 | Offstage(
188 | offstage: model.isShowSuspension != true,
189 | child: _buildSusWidget(susTag),
190 | ),
191 | SizedBox(
192 | height: _itemHeight.toDouble(),
193 | child: ListTile(
194 | title: Text(model.name),
195 | onTap: (){
196 | print('onItemClick:$model');
197 | Navigator.pop(context, model.name);
198 | },
199 | ),
200 | )
201 | ],
202 | );
203 | }
204 | }
205 |
206 | class CityInfo extends ISuspensionBean {
207 | String name;
208 | String tagIndex;
209 | String namePinyin;
210 |
211 | CityInfo({
212 | this.name,
213 | this.tagIndex,
214 | this.namePinyin,
215 | });
216 |
217 | CityInfo.fromJson(Map json)
218 | : name = json['name'] == null ? "" : json['name'];
219 |
220 | Map toJson() => {
221 | 'name': name,
222 | 'tagIndex': tagIndex,
223 | 'namePinyin': namePinyin,
224 | 'isShowSuspension': isShowSuspension
225 | };
226 |
227 | @override
228 | String getSuspensionTag() => tagIndex;
229 |
230 | @override
231 | String toString() => "CityBean {" + " \"name\":\"" + name + "\"" + '}';
232 | }
233 |
--------------------------------------------------------------------------------
/lib/pages/home_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_swiper/flutter_swiper.dart';
3 | import 'package:flutter_wtrip/dao/home_dao.dart';
4 | import 'package:flutter_wtrip/model/common_model.dart';
5 | import 'package:flutter_wtrip/model/gridNav_model.dart';
6 |
7 | import 'package:flutter_wtrip/model/home_model.dart';
8 | import 'package:flutter_wtrip/model/sales_box_model.dart';
9 | import 'package:flutter_wtrip/pages/search_page.dart';
10 | import 'package:flutter_wtrip/widgets/grid_nav.dart';
11 | import 'package:flutter_wtrip/widgets/loading_container.dart';
12 | import 'package:flutter_wtrip/widgets/local_nav.dart';
13 | import 'package:flutter_wtrip/widgets/sales_box.dart';
14 | import 'package:flutter_wtrip/widgets/search_bar.dart';
15 | import 'package:flutter_wtrip/widgets/sub_nav.dart';
16 | import 'package:flutter_wtrip/widgets/webview.dart';
17 | import 'package:flutter_wtrip/pages/city_page.dart';
18 | import 'package:flutter_wtrip/util/navigator_util.dart';
19 | import 'package:flutter_wtrip/widgets/cached_image.dart';
20 | import 'package:flutter_wtrip/pages/speak_page.dart';
21 |
22 | const APPBAR_SCROLL_OFFSET = 100;
23 | const SEARCH_BAR_DEFAULT_TEXT = '网红打卡地 景点 酒店 美食';
24 |
25 | class HomePage extends StatefulWidget {
26 | @override
27 | _HomePageState createState() => _HomePageState();
28 | }
29 |
30 | class _HomePageState extends State
31 | with AutomaticKeepAliveClientMixin {
32 | double appBarAlpha = 0;
33 |
34 | String resultString = '';
35 |
36 | List localNavList = [];
37 | List bannerList = [];
38 | List subNavList = [];
39 |
40 | GridNavModel gridNavModel;
41 | SalesBoxModel salesBoxModel;
42 |
43 | // 加载状态
44 | bool isLoading = true;
45 |
46 | String city = '北京市';
47 |
48 | @override
49 | void initState() {
50 | super.initState();
51 | _handleRefresh();
52 | }
53 |
54 | //缓存页面
55 | @override
56 | bool get wantKeepAlive => true;
57 |
58 | Future _handleRefresh() async {
59 | // HomeDao.fetch().then((result){
60 | // setState(() {
61 | // resultString = json.encode(result);
62 | // });
63 | // }).catchError((e){
64 | // setState(() {
65 | // resultString = json.encode(e);
66 | // });
67 | // });
68 | try {
69 | HomeModel model = await HomeDao.fetch();
70 | setState(() {
71 | localNavList = model.localNavList;
72 | gridNavModel = model.gridNav;
73 | subNavList = model.subNavList;
74 | salesBoxModel = model.salesBox;
75 | bannerList = model.bannerList;
76 | isLoading = false;
77 | });
78 |
79 | // print(model.config.searchUrl);
80 | } catch (e) {
81 | print(e);
82 | setState(() {
83 | isLoading = false;
84 | });
85 | }
86 |
87 | return null;
88 | }
89 |
90 | //判断滚动改变透明度
91 | _onScroll(offset) {
92 | double alpha = offset / APPBAR_SCROLL_OFFSET;
93 | if (alpha < 0) {
94 | alpha = 0;
95 | } else if (alpha > 1) {
96 | alpha = 1;
97 | }
98 |
99 | setState(() {
100 | appBarAlpha = alpha;
101 | });
102 | }
103 |
104 | // 跳转至城市页面
105 | _jumpToCity() async {
106 | String result = await NavigatorUtil.push(context, CityPage());
107 |
108 | if (result != null) {
109 | setState(() {
110 | city = result;
111 | });
112 | }
113 | }
114 |
115 | // 跳转搜索页面
116 | _jumpToSearch() {
117 | NavigatorUtil.push(context, SearchPage(hint: SEARCH_BAR_DEFAULT_TEXT));
118 | }
119 |
120 | // 跳转语音页面
121 | _jumpToSpeak() {
122 | NavigatorUtil.push(context, SpeakPage());
123 | }
124 |
125 | @override
126 | Widget build(BuildContext context) {
127 | // TODO: implement build
128 | return Scaffold(
129 | backgroundColor: Color(0xfff2f2f2),
130 | body: LoadingContainer(
131 | isLoading: isLoading,
132 | child: Stack(
133 | children: [
134 | MediaQuery.removePadding(
135 | // 去除手机顶部的padding
136 | removeTop: true,
137 | context: context,
138 | child: RefreshIndicator(
139 | onRefresh: _handleRefresh,
140 | child: NotificationListener(
141 | onNotification: (scrollNotification) {
142 | if (scrollNotification is ScrollUpdateNotification &&
143 | scrollNotification.depth == 0) {
144 | // 滑动滚动是第0个元素,也就是列表滚动的时候才监听
145 | _onScroll(scrollNotification.metrics.pixels);
146 | }
147 | return true;
148 | },
149 | child: _listView,
150 | ),
151 | )),
152 | _appBar,
153 | ],
154 | )),
155 | );
156 | }
157 |
158 | // 顶部搜索框
159 | Widget get _appBar {
160 | return Column(
161 | children: [
162 | Container(
163 | // 装饰器
164 | decoration: BoxDecoration(
165 | // 线性渐变
166 | gradient: LinearGradient(
167 | colors: [Color(0x66000000), Colors.transparent],
168 | begin: Alignment.topCenter,
169 | end: Alignment.bottomCenter,
170 | )),
171 | child: Container(
172 | padding: EdgeInsets.fromLTRB(0, 20, 0, 0),
173 | height: 80,
174 | decoration: BoxDecoration(
175 | color: Color.fromARGB((appBarAlpha * 255).toInt(), 255, 255, 255),
176 | ),
177 | child: SearchBar(
178 | searchBarType: appBarAlpha > 0.2
179 | ? SearchBarType.homeLight
180 | : SearchBarType.home,
181 | defaultText: SEARCH_BAR_DEFAULT_TEXT,
182 | inputBoxClick: _jumpToSearch,
183 | speakClick: _jumpToSpeak,
184 | leftButtonClick: _jumpToCity,
185 | city: city,
186 | ),
187 | ),
188 | ),
189 | // 底部阴影
190 | Container(
191 | height: appBarAlpha > 0.2 ? 0.5 : 0,
192 | decoration: BoxDecoration(
193 | boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 0.5)]),
194 | )
195 | ],
196 | );
197 | }
198 |
199 | // banner轮播图
200 | Widget get _banner {
201 | return Container(
202 | height: 160,
203 | child: Swiper(
204 | itemCount: bannerList.length,
205 | autoplay: true, // 自动播放
206 | itemBuilder: (BuildContext context, int index) {
207 | return GestureDetector(
208 | // 点击事件
209 | onTap: () {
210 | NavigatorUtil.push(
211 | context,
212 | WebView(
213 | url: bannerList[index].url,
214 | statusBarColor: bannerList[index].statusBarColor,
215 | hideAppBar: bannerList[index].hideAppBar,
216 | ));
217 | },
218 | child: CachedImage(
219 | imageUrl: bannerList[index].icon,
220 | fit: BoxFit.fill,
221 | ),
222 | );
223 | },
224 | pagination: SwiperPagination(), // 指示器
225 | ),
226 | );
227 | }
228 |
229 | Widget get _listView {
230 | return ListView(
231 | physics: const AlwaysScrollableScrollPhysics(),
232 | children: [
233 | _banner,
234 | Padding(
235 | child: LocalNav(
236 | localNavList: localNavList,
237 | ),
238 | padding: EdgeInsets.fromLTRB(7, 4, 7, 4),
239 | ),
240 | Padding(
241 | child: GridNav(
242 | gridNavModel: gridNavModel,
243 | ),
244 | padding: EdgeInsets.fromLTRB(7, 0, 7, 4),
245 | ),
246 | Padding(
247 | child: SubNav(
248 | subNavList: subNavList,
249 | ),
250 | padding: EdgeInsets.fromLTRB(7, 0, 7, 4),
251 | ),
252 | Padding(
253 | child: SalesBox(
254 | salesBox: salesBoxModel,
255 | ),
256 | padding: EdgeInsets.fromLTRB(7, 0, 7, 4),
257 | ),
258 | ],
259 | );
260 | }
261 | }
262 |
--------------------------------------------------------------------------------
/lib/pages/my_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_wtrip/widgets/webview.dart';
3 |
4 | class MyPage extends StatefulWidget{
5 | @override
6 | _MyPageState createState() => _MyPageState();
7 | }
8 |
9 | class _MyPageState extends State{
10 | @override
11 | Widget build(BuildContext context) {
12 | // TODO: implement build
13 | return Scaffold(
14 | body: WebView(
15 | url: 'https://m.ctrip.com/webapp/myctrip/',
16 | hideAppBar: true,
17 | backForbid: true,
18 | statusBarColor: '4c5bca',
19 | ),
20 | );
21 | }
22 |
23 | }
--------------------------------------------------------------------------------
/lib/pages/search_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_wtrip/dao/search_dao.dart';
3 | import 'package:flutter_wtrip/model/search_model.dart';
4 | import 'package:flutter_wtrip/widgets/search_bar.dart';
5 | import 'package:flutter_wtrip/widgets/webview.dart';
6 |
7 | const URL =
8 | 'https://m.ctrip.com/restapi/h5api/searchapp/search?source=mobileweb&action=autocomplete&contentType=json&keyword=';
9 |
10 | class SearchPage extends StatefulWidget {
11 | final bool hideLeft;
12 | final String searchUrl;
13 | final String keyWord;
14 | final String hint;
15 |
16 | const SearchPage(
17 | {Key key, this.hideLeft, this.searchUrl = URL, this.keyWord, this.hint})
18 | : super(key: key);
19 |
20 | @override
21 | _SearchPageState createState() => _SearchPageState();
22 | }
23 |
24 | class _SearchPageState extends State {
25 | SearchModel searchModel;
26 | String keyWord;
27 |
28 | @override
29 | Widget build(BuildContext context) {
30 | // TODO: implement build
31 | return Scaffold(
32 | body: Column(
33 | children: [
34 | _appBar(context),
35 | MediaQuery.removePadding(
36 | removeTop: true,
37 | context: context,
38 | child: Expanded(
39 | flex: 1,
40 | child: ListView.builder(
41 | itemCount: searchModel?.data?.length ?? 0,
42 | itemBuilder: (BuildContext context, int position) {
43 | return _item(position);
44 | })))
45 | ],
46 | ),
47 | );
48 | }
49 |
50 | // 搜索结果item
51 | _item(int pos) {
52 | if (searchModel == null || searchModel.data == null) return null;
53 | SearchItem item = searchModel.data[pos];
54 | return GestureDetector(
55 | onTap: () {
56 | Navigator.push(
57 | context,
58 | MaterialPageRoute(
59 | builder: (context) => (WebView(
60 | url: item.url,
61 | title: '详情',
62 | ))));
63 | },
64 | child: Container(
65 | padding: EdgeInsets.all(10),
66 | decoration: BoxDecoration(
67 | border: Border(bottom: BorderSide(color: Colors.grey, width: 0.3))),
68 | child: Row(
69 | children: [
70 | Column(
71 | children: [
72 | Container(
73 | width: 300,
74 | child: _title(item),
75 | ),
76 | Container(
77 | margin: EdgeInsets.only(top: 5),
78 | width: 300,
79 | child: _subTitle(item),
80 | )
81 | ],
82 | )
83 | ],
84 | ),
85 | ),
86 | );
87 | }
88 | // 主标题
89 | _title(SearchItem item) {
90 | if (item == null) return null;
91 | List spans = [];
92 | spans.addAll(_keywordTextSpans(item.word, searchModel.keyword));
93 | spans.add(TextSpan(
94 | text: ' ' + (item.districtname??'') + ' ' +(item.zonename??''),
95 | style: TextStyle(
96 | color: Colors.grey,
97 | fontSize: 16,
98 | )));
99 |
100 | return RichText(
101 | text: TextSpan(
102 | children: spans,
103 | ),
104 | );
105 | }
106 | // 副标题
107 | _subTitle(SearchItem item) {
108 | if (item == null) return null;
109 | return RichText(
110 | text: TextSpan(
111 | children: [
112 | TextSpan(
113 | text: item.price??"",
114 | style: TextStyle(color: Colors.orange, fontSize: 16),
115 | ),
116 | TextSpan(
117 | text: " " + (item.star??""),
118 | style: TextStyle(color: Colors.orange, fontSize: 16),
119 | ),
120 | ]
121 | ),
122 | );
123 | }
124 | // 富文本文字高亮
125 | _keywordTextSpans(String word, String keyword) {
126 | if (word == null || word.length == 0) return null;
127 | List spans = [];
128 | List arr = word.split(keyword);
129 | for (int i = 0; i < arr.length; i++) {
130 | if ((i + 1) % 2 == 0) {
131 | spans.add(TextSpan(
132 | text: keyword,
133 | style: TextStyle(color: Colors.orange, fontSize: 16)));
134 | }
135 | String val = arr[i];
136 | if (val != null && val.length > 0) {
137 | spans.add(TextSpan(
138 | text: val, style: TextStyle(color: Colors.black87, fontSize: 16)));
139 | }
140 | }
141 | return spans;
142 | }
143 |
144 | _appBar(BuildContext context) {
145 | return Column(
146 | children: [
147 | Container(
148 | decoration: BoxDecoration(
149 | gradient: LinearGradient(
150 | colors: [Color(0x66000000), Colors.transparent],
151 | begin: Alignment.topCenter,
152 | end: Alignment.bottomCenter,
153 | )),
154 | child: Container(
155 | padding: EdgeInsets.only(top: 20),
156 | height: 80,
157 | decoration: BoxDecoration(color: Colors.white),
158 | child: SearchBar(
159 | hideLeft: widget.hideLeft,
160 | hint: widget.hint,
161 | defaultText: widget.keyWord,
162 | leftButtonClick: () {
163 | Navigator.pop(context);
164 | },
165 | onChange: _onChangeText,
166 | searchBarType: SearchBarType.normal,
167 | ),
168 | ),
169 | )
170 | ],
171 | );
172 | }
173 |
174 | _onChangeText(String text) async{
175 | keyWord = text;
176 | if (text.length == 0) {
177 | setState(() {
178 | searchModel = null;
179 | });
180 | return;
181 | }
182 |
183 | try{
184 | SearchModel model = await SearchDao.fetch(text);
185 | if (model.keyword == keyWord) {
186 | setState(() {
187 | searchModel = model;
188 | });
189 | }
190 | }catch(e){
191 | print(e);
192 | }
193 |
194 | // SearchDao.fetch(text).then((SearchModel modal) {
195 | // if (modal.keyword == keyWord) {
196 | // setState(() {
197 | // searchModel = modal;
198 | // });
199 | // }
200 | // }).catchError((e) {
201 | // print(e);
202 | // });
203 | }
204 | }
205 |
--------------------------------------------------------------------------------
/lib/pages/speak_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_wtrip/pages/search_page.dart';
3 | import 'package:flutter_wtrip/util/navigator_util.dart';
4 | import 'package:flutter_wtrip/plugin/asr_manager.dart';
5 |
6 | class SpeakPage extends StatefulWidget {
7 | @override
8 | _SpeakPageState createState() => _SpeakPageState();
9 | }
10 |
11 | class _SpeakPageState extends State
12 | with SingleTickerProviderStateMixin {
13 | String speakTips = '长按说话';
14 | String speakResult = '';
15 |
16 | Animation animation;
17 | AnimationController controller;
18 |
19 | @override
20 | void initState() {
21 | controller = AnimationController(
22 | vsync: this, duration: Duration(milliseconds: 1000));
23 | animation = CurvedAnimation(parent: controller, curve: Curves.easeIn)
24 | ..addStatusListener((status) {
25 | if (status == AnimationStatus.completed) {
26 | controller.reverse();
27 | } else if (status == AnimationStatus.dismissed) {
28 | controller.forward();
29 | }
30 | });
31 | super.initState();
32 | }
33 |
34 | @override
35 | void dispose() {
36 | // TODO: implement dispose
37 | controller.dispose();
38 | super.dispose();
39 | }
40 |
41 | // 开始录音
42 | void _speakStart() {
43 | controller.forward();
44 | setState(() {
45 | speakTips = '识别中...';
46 | });
47 |
48 | AsrManager.start().then((text) {
49 | print('---识别的文字:'+text);
50 | if (text != null && text.length > 0) {
51 | setState(() {
52 | speakResult = text;
53 | });
54 | Navigator.pop(context);
55 | NavigatorUtil.push(
56 | context,
57 | SearchPage(
58 | keyWord: speakResult,
59 | ));
60 | }
61 | }).catchError((e) {
62 | print('-------' + e.toString());
63 | });
64 | }
65 |
66 | // 停止录音
67 | void _speakStop() {
68 | setState(() {
69 | speakTips = '长按说话';
70 | });
71 |
72 | controller.reset();
73 | controller.stop();
74 | AsrManager.stop();
75 | }
76 |
77 | @override
78 | Widget build(BuildContext context) {
79 | return Scaffold(
80 | body: Container(
81 | padding: EdgeInsets.all(30),
82 | child: Center(
83 | child: Column(
84 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
85 | children: [
86 | _topItem,
87 | _bottomItem,
88 | ],
89 | ),
90 | ),
91 | ),
92 | );
93 | }
94 |
95 | Widget get _topItem {
96 | return Column(
97 | children: [
98 | Padding(
99 | padding: EdgeInsets.fromLTRB(0, 30, 0, 30),
100 | child: Text(
101 | '你可以这样说',
102 | style: TextStyle(fontSize: 16, color: Colors.black54),
103 | ),
104 | ),
105 | Text(
106 | '故宫门票\n北京一日游\n迪士尼乐园',
107 | textAlign: TextAlign.center,
108 | style: TextStyle(fontSize: 15, color: Colors.grey),
109 | ),
110 | Padding(
111 | padding: EdgeInsets.all(20),
112 | child: Text(
113 | speakResult,
114 | style: TextStyle(color: Colors.blue),
115 | ),
116 | )
117 | ],
118 | );
119 | }
120 |
121 | Widget get _bottomItem {
122 | return FractionallySizedBox(
123 | child: Stack(
124 | children: [
125 | GestureDetector(
126 | onTapDown: (e) {
127 | _speakStart();
128 | },
129 | onTapUp: (e) {
130 | _speakStop();
131 | },
132 | onTapCancel: () {
133 | _speakStop();
134 | },
135 | child: Center(
136 | child: Column(
137 | children: [
138 | Padding(
139 | padding: EdgeInsets.all(10),
140 | child: Text(
141 | speakTips,
142 | style: TextStyle(color: Colors.blue, fontSize: 12),
143 | ),
144 | ),
145 | Stack(
146 | children: [
147 | Container(
148 | height: MIC_SIZE,
149 | width: MIC_SIZE,
150 | ),
151 | Center(
152 | child: AnimatedMic(
153 | animation: animation,
154 | ),
155 | )
156 | ],
157 | )
158 | ],
159 | ),
160 | ),
161 | ),
162 | Positioned(
163 | right: 0,
164 | bottom: 20,
165 | child: GestureDetector(
166 | onTap: () {
167 | Navigator.pop(context);
168 | },
169 | child: Icon(
170 | Icons.close,
171 | size: 30,
172 | color: Colors.grey,
173 | ),
174 | ),
175 | ),
176 | // Text(
177 | // '百度AI语音平台提供技术支持',
178 | // style: TextStyle(
179 | // fontSize: 12,
180 | // color: Color.fromARGB(255, 200, 200, 200)
181 | // ),
182 | // )
183 | ],
184 | ),
185 | );
186 | }
187 | }
188 |
189 | const double MIC_SIZE = 80;
190 |
191 | class AnimatedMic extends AnimatedWidget {
192 | static final _operatyTween = Tween(begin: 1, end: 0.5);
193 | static final _sizeTween = Tween(begin: MIC_SIZE, end: MIC_SIZE - 20);
194 |
195 | AnimatedMic({Key key, Animation animation})
196 | : super(key: key, listenable: animation);
197 |
198 | @override
199 | Widget build(BuildContext context) {
200 | final Animation animation = listenable;
201 |
202 | return Opacity(
203 | opacity: _operatyTween.evaluate(animation),
204 | child: Container(
205 | height: _sizeTween.evaluate(animation),
206 | width: _sizeTween.evaluate(animation),
207 | decoration: BoxDecoration(
208 | color: Colors.blue,
209 | borderRadius: BorderRadius.circular(MIC_SIZE / 2)),
210 | child: Icon(
211 | Icons.mic,
212 | color: Colors.white,
213 | size: 30,
214 | ),
215 | ),
216 | );
217 | }
218 | }
219 |
--------------------------------------------------------------------------------
/lib/pages/travel_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_wtrip/dao/travel_tab_dao.dart';
3 | import 'package:flutter_wtrip/model/travel_tab_model.dart';
4 | import 'package:flutter_wtrip/pages/travel_tab_page.dart';
5 |
6 | const DEFAULT_URL =
7 | 'https://m.ctrip.com/restapi/soa2/16189/json/searchTripShootListForHomePageV2?_fxpcqlniredt=09031014111431397988&__gw_appid=99999999&__gw_ver=1.0&__gw_from=10650013707&__gw_platform=H5';
8 | const PAGE_SIZE = 10;
9 |
10 | class TravelPage extends StatefulWidget {
11 | @override
12 | _TravelPageState createState() => _TravelPageState();
13 | }
14 |
15 | class _TravelPageState extends State with TickerProviderStateMixin {
16 | TabController _tabController;
17 | TravelTabModel travelTabModel;
18 | List tabs = [];
19 |
20 | @override
21 | void initState() {
22 | _tabController = TabController(length: 0, vsync: this);
23 | TravelTabDao.fetch().then((TravelTabModel model) {
24 | // 重新设置coller 修复标签不显示
25 | _tabController = TabController(length: model.tabs.length, vsync: this);
26 | setState(() {
27 | tabs = model.tabs;
28 | travelTabModel = model;
29 | });
30 | }).catchError((e) {
31 | print(e);
32 | });
33 |
34 | super.initState();
35 | }
36 |
37 | @override
38 | void dispose() {
39 | _tabController.dispose();
40 | super.dispose();
41 | }
42 |
43 | @override
44 | Widget build(BuildContext context) {
45 | // TODO: implement build
46 | return Scaffold(
47 | body: Column(
48 | children: [
49 | Container(
50 | color: Colors.white,
51 | padding: EdgeInsets.only(top: 30),
52 | child: TabBar(
53 | controller: _tabController,
54 | isScrollable: true,
55 | labelColor: Colors.black,
56 | labelPadding: EdgeInsets.fromLTRB(20, 0, 20, 5),
57 | indicatorSize: TabBarIndicatorSize.label,
58 | indicator: UnderlineTabIndicator(
59 | borderSide: BorderSide(
60 | color: Color(0xff2fcfbb),
61 | width: 3,
62 | ),
63 | insets: EdgeInsets.only(bottom: 10)),
64 | tabs: tabs.map((TravelTab tab) {
65 | return Tab(
66 | text: tab.labelName,
67 | );
68 | }).toList(), // 将map装换成list
69 | ),
70 | ),
71 | Flexible(
72 | child: TabBarView(
73 | controller: _tabController,
74 | children: tabs.map((TravelTab tab) {
75 | return TravelTabPage(travelUrl: travelTabModel.url, groupChannelCode: tab.groupChannelCode,);
76 | }).toList()))
77 | ],
78 | ),
79 | );
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/lib/pages/travel_tab_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_wtrip/dao/travel_page_dao.dart';
3 | import 'package:flutter_wtrip/model/travel_page_model.dart';
4 | import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
5 | import 'package:flutter_wtrip/widgets/loading_container.dart';
6 | import 'package:flutter_wtrip/widgets/webview.dart';
7 |
8 | const DEFAULT_URL =
9 | 'https://m.ctrip.com/restapi/soa2/16189/json/searchTripShootListForHomePageV2?_fxpcqlniredt=09031014111431397988&__gw_appid=99999999&__gw_ver=1.0&__gw_from=10650013707&__gw_platform=H5';
10 | const PAGE_SIZE = 10;
11 |
12 | class TravelTabPage extends StatefulWidget {
13 | final String groupChannelCode;
14 | final String travelUrl;
15 |
16 | const TravelTabPage({Key key, this.groupChannelCode, this.travelUrl})
17 | : super(key: key);
18 |
19 | @override
20 | _TravelTabPageState createState() => _TravelTabPageState();
21 | }
22 |
23 | /// with AutomaticKeepAliveClientMixin 加载过的页面重绘
24 | class _TravelTabPageState extends State
25 | with AutomaticKeepAliveClientMixin {
26 | List travelPageItems = [];
27 | int pageIndex = 1;
28 | bool _isLoading = true;
29 | bool showToTopBtn = false;
30 |
31 | ScrollController _scrollController = ScrollController();
32 |
33 | @override
34 | void initState() {
35 | _loadData();
36 | _scrollController.addListener(() {
37 | if (_scrollController.offset < 1000 && showToTopBtn) {
38 | setState(() {
39 | showToTopBtn = false;
40 | });
41 | } else if (_scrollController.offset >= 1000 && showToTopBtn == false) {
42 | setState(() {
43 | showToTopBtn = true;
44 | });
45 | }
46 |
47 | if (_scrollController.position.pixels ==
48 | _scrollController.position.maxScrollExtent) {
49 | _loadData(isLoadMore: true);
50 | }
51 | });
52 | super.initState();
53 | }
54 |
55 | // 刷新是一个异步方法
56 | Future _handleRefresh() async {
57 | _loadData();
58 |
59 | return null;
60 | }
61 |
62 | @override
63 | Widget build(BuildContext context) {
64 | // TODO: implement build
65 | return Scaffold(
66 | body: LoadingContainer(
67 | isLoading: _isLoading,
68 | child: RefreshIndicator(
69 | child: MediaQuery.removePadding(
70 | removeTop: true,
71 | context: context,
72 | child: StaggeredGridView.countBuilder(
73 | controller: _scrollController,
74 | crossAxisCount: 4,
75 | itemCount: travelPageItems?.length ?? 0,
76 | itemBuilder: (BuildContext context, int index) => _TravelItem(
77 | index: index,
78 | item: travelPageItems[index],
79 | ),
80 | staggeredTileBuilder: (int index) => new StaggeredTile.fit(2),
81 | )),
82 | onRefresh: _handleRefresh),
83 | ),
84 | // 显示一个返回顶部按钮
85 | floatingActionButton: !showToTopBtn
86 | ? null
87 | : FloatingActionButton(
88 | child: Icon(Icons.arrow_upward),
89 | onPressed: () {
90 | _scrollController.animateTo(.0,
91 | duration: Duration(microseconds: 200), curve: Curves.ease);
92 | },
93 | ),
94 | );
95 | }
96 |
97 | /// Dart 可选参数
98 | void _loadData({isLoadMore = false}) async {
99 | if (isLoadMore) {
100 | pageIndex++;
101 | } else {
102 | pageIndex = 1;
103 | }
104 |
105 | try {
106 | TravelPageModel model = await TravelPageDao.fetch(
107 | widget.travelUrl ?? DEFAULT_URL,
108 | widget.groupChannelCode,
109 | pageIndex,
110 | PAGE_SIZE);
111 | _isLoading = false;
112 |
113 | setState(() {
114 | List items = _filterItems(model.resultList);
115 | if (travelPageItems != null) {
116 | travelPageItems.addAll(items);
117 | } else {
118 | travelPageItems = items;
119 | }
120 | });
121 | } catch (e) {
122 | print(e);
123 | _isLoading = false;
124 | }
125 | }
126 |
127 | /// 过滤服务器返回结果, 移除article为空
128 | List _filterItems(List resultList) {
129 | if (resultList == null) {
130 | return [];
131 | }
132 | List filterItems = [];
133 | resultList.forEach((item) {
134 | if (item.article != null) {
135 | filterItems.add(item);
136 | }
137 | });
138 |
139 | return filterItems;
140 | }
141 |
142 | @override
143 | // TODO: implement wantKeepAlive
144 | bool get wantKeepAlive => true;
145 | }
146 |
147 | class _TravelItem extends StatelessWidget {
148 | final int index;
149 | final TravelPageItem item;
150 |
151 | const _TravelItem({Key key, this.index, this.item}) : super(key: key);
152 |
153 | @override
154 | Widget build(BuildContext context) {
155 | // TODO: implement build
156 | return GestureDetector(
157 | onTap: () {
158 | Navigator.push(
159 | context,
160 | MaterialPageRoute(
161 | builder: (context) => (WebView(
162 | url: item.article.urls[0].h5Url,
163 | title: '旅拍详情',
164 | ))));
165 | },
166 | child: Card(
167 | /// 裁切圆角
168 | child: PhysicalModel(
169 | color: Colors.transparent,
170 | clipBehavior: Clip.antiAlias,
171 | borderRadius: BorderRadius.circular(5),
172 | child: Column(
173 | crossAxisAlignment: CrossAxisAlignment.start, // 设置子widget未知
174 | children: [
175 | _itemImage(),
176 | Container(
177 | padding: EdgeInsets.all(4),
178 | child: Text(
179 | item.article.articleTitle,
180 | maxLines: 2,
181 | overflow: TextOverflow.ellipsis,
182 | style: TextStyle(color: Colors.black87, fontSize: 14),
183 | ),
184 | ),
185 | _infoText(),
186 | ],
187 | ),
188 | ),
189 | ),
190 | );
191 | }
192 |
193 | _infoText() {
194 | return Container(
195 | padding: EdgeInsets.fromLTRB(6, 0, 6, 10),
196 | child: Row(
197 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
198 | children: [
199 | Row(
200 | children: [
201 | PhysicalModel(
202 | color: Colors.transparent,
203 | clipBehavior: Clip.antiAlias,
204 | borderRadius: BorderRadius.circular(12),
205 | child: Image.network(
206 | item.article.author?.coverImage?.dynamicUrl,
207 | width: 24,
208 | height: 24,
209 | ),
210 | ),
211 | Container(
212 | padding: EdgeInsets.all(5),
213 | width: 90,
214 | child: Text(
215 | item.article.articleTitle,
216 | maxLines: 1,
217 | overflow: TextOverflow.ellipsis,
218 | style: TextStyle(
219 | color: Colors.black87,
220 | fontSize: 12,
221 | ),
222 | ),
223 | )
224 | ],
225 | ),
226 | Row(
227 | children: [
228 | Icon(
229 | Icons.thumb_up,
230 | size: 14,
231 | color: Colors.grey,
232 | ),
233 | Padding(
234 | padding: EdgeInsets.only(left: 3),
235 | child: Text(item.article.likeCount.toString()),
236 | )
237 | ],
238 | )
239 | ],
240 | ),
241 | );
242 | }
243 |
244 | _itemImage() {
245 | return Stack(
246 | children: [
247 | Image.network(item.article.images[0]?.dynamicUrl),
248 | Positioned(
249 | bottom: 8,
250 | left: 8,
251 | child: Container(
252 | padding: EdgeInsets.fromLTRB(5, 1, 5, 1),
253 | decoration: BoxDecoration(
254 | color: Colors.black54, borderRadius: BorderRadius.circular(10)),
255 | child: Row(
256 | children: [
257 | Padding(
258 | padding: EdgeInsets.only(right: 3),
259 | child: Icon(
260 | Icons.location_on,
261 | color: Colors.white,
262 | size: 12,
263 | ),
264 | ),
265 | LimitedBox(
266 | // 显示带缩略号的文字
267 | maxWidth: 130,
268 | child: Text(
269 | _poiName(),
270 | maxLines: 1,
271 | overflow: TextOverflow.ellipsis,
272 | style: TextStyle(color: Colors.white, fontSize: 12),
273 | ),
274 | )
275 | ],
276 | ),
277 | ),
278 | )
279 | ],
280 | );
281 | }
282 |
283 | _poiName() {
284 | return item.article.pois == null || item.article.pois.length == 0
285 | ? '未知'
286 | : item.article.pois[0]?.poiName ?? '未知';
287 | }
288 | }
289 |
--------------------------------------------------------------------------------
/lib/plugin/asr_manager.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/services.dart';
2 |
3 | // 百度ai语音flutter方法
4 | class AsrManager {
5 | static const MethodChannel _channel = const MethodChannel('asr_plugin');
6 |
7 | // 开始录音
8 | static Future start({Map params}) async {
9 | return await _channel.invokeMethod('start', params ??{});
10 | }
11 | // 停止录音
12 | static Future stop() async {
13 | return await _channel.invokeMethod('stop');
14 | }
15 | // 取消录音
16 | static Future cancel() async {
17 | return await _channel.invokeMethod('cancel');
18 | }
19 |
20 | }
--------------------------------------------------------------------------------
/lib/util/navigator_util.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/widgets.dart';
3 | // 页面跳转工具类
4 | class NavigatorUtil {
5 | // 跳转页面
6 | static push(BuildContext context, Widget page) async {
7 | final result = await Navigator.push(
8 | context, MaterialPageRoute(builder: (context) => page));
9 | return result;
10 | }
11 | // 使用命名路由进行跳转
12 | static pushNamed(BuildContext context, String routeName) async {
13 | final result = await Navigator.pushNamed(context, routeName);
14 | return result;
15 | }
16 | // 返回上一页
17 | static pop(BuildContext context) {
18 | Navigator.pop(context);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/lib/widgets/cached_image.dart:
--------------------------------------------------------------------------------
1 | import 'package:cached_network_image/cached_network_image.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | class CachedImage extends StatelessWidget {
5 | final AlignmentGeometry alignment;
6 | final BoxFit fit;
7 | final String imageUrl;
8 | final double width;
9 | final double height;
10 | final bool inSizedBox;
11 |
12 | const CachedImage(
13 | {Key key,
14 | @required this.imageUrl,
15 | this.alignment = Alignment.center,
16 | this.fit,
17 | this.width,
18 | this.height,
19 | this.inSizedBox = false})
20 | : assert(imageUrl != null),
21 | super(key: key);
22 |
23 | @override
24 | Widget build(BuildContext context) {
25 | return inSizedBox
26 | ? FractionallySizedBox(
27 | widthFactor: 1,
28 | child: CachedNetworkImage(
29 | imageUrl: imageUrl,
30 | alignment: alignment,
31 | fit: fit,
32 | width: width,
33 | height: height,
34 | ),
35 | )
36 | : CachedNetworkImage(
37 | imageUrl: imageUrl,
38 | alignment: alignment,
39 | fit: fit,
40 | width: width,
41 | height: height,
42 | );
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/lib/widgets/grid_nav.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_wtrip/model/common_model.dart';
3 | import 'package:flutter_wtrip/model/gridNav_model.dart';
4 | import 'package:flutter_wtrip/widgets/webview.dart';
5 |
6 | class GridNav extends StatelessWidget {
7 | final GridNavModel gridNavModel;
8 |
9 | const GridNav({Key key, this.gridNavModel}) : super(key: key);
10 |
11 | @override
12 | Widget build(BuildContext context) {
13 | // TODO: implement build
14 | return PhysicalModel(
15 | color: Colors.transparent,// 透明
16 | borderRadius: BorderRadius.circular(6), // 圆角
17 | clipBehavior: Clip.antiAlias, // 裁切
18 | child: Column(
19 | children: _gridNavItems(context),
20 | ),
21 | );
22 | }
23 |
24 | _gridNavItems(BuildContext context) {
25 | // 整个卡片
26 | List items = [];
27 | if (gridNavModel == null) return items;
28 | if (gridNavModel.hotel != null) {
29 | items.add(_gridNavItem(context,gridNavModel.hotel, true));
30 | }
31 | if (gridNavModel.flight != null) {
32 | items.add(_gridNavItem(context,gridNavModel.flight, false));
33 | }
34 | if (gridNavModel.travel != null) {
35 | items.add(_gridNavItem(context,gridNavModel.travel, false));
36 | }
37 |
38 | return items;
39 | }
40 |
41 | _gridNavItem(BuildContext context, GridNavItem item, bool isFirst) {
42 | // 卡片的三块
43 | List items = [];
44 | items.add(_mainItem(context, item.mainItem));
45 | items.add(_doubleItem(context, item.item1, item.item2));
46 | items.add(_doubleItem(context, item.item3, item.item4));
47 |
48 | List expandItems = [];
49 | items.forEach((item){
50 | expandItems.add(Expanded(child: item, flex: 1,));
51 | });
52 |
53 | Color startColor = Color(int.parse('0xff'+item.startColor));
54 | Color endColor = Color(int.parse('0xff'+item.endColor));
55 |
56 | return Container(
57 | height: 88,
58 | margin: isFirst? null: EdgeInsets.only(top: 3),
59 | decoration: BoxDecoration(
60 | // 颜色渐变
61 | gradient: LinearGradient(colors: [startColor, endColor])
62 | ),
63 | child: Row(
64 | children: expandItems,
65 | ),
66 | );
67 | }
68 | // 最左边的卡片
69 | _mainItem(BuildContext context, CommonModel model) {
70 | return _wrapGesture(context, Stack(
71 | alignment: AlignmentDirectional.topCenter,
72 | children: [
73 | Image.network(
74 | model.icon,
75 | fit: BoxFit.contain,
76 | width: 88,
77 | height: 121,
78 | alignment: AlignmentDirectional.bottomEnd,
79 | ),
80 | Container(
81 | margin: EdgeInsets.only(top: 11),
82 | child: Text(
83 | model.title,
84 | style: TextStyle(fontSize: 14, color: Colors.white),
85 | ),
86 | )
87 | ],
88 | ), model);
89 | }
90 | // 小卡片竖直排列的两块
91 | _doubleItem(BuildContext context, CommonModel topModel,
92 | CommonModel bottomModel) {
93 | return Column(
94 | children: [
95 | Expanded(
96 | child: _item(context, topModel, true),
97 | ),
98 | Expanded(
99 | child: _item(context, bottomModel, false),
100 | )
101 | ],
102 | );
103 | }
104 | // 单独的每个小卡片
105 | _item(BuildContext context, CommonModel item, bool isFirst) {
106 | BorderSide borderSide = BorderSide(width: 0.8, color: Colors.white);
107 | return FractionallySizedBox(
108 | // 撑满父布局
109 | widthFactor: 1,
110 | child: Container(
111 | decoration: BoxDecoration(
112 | border: Border(
113 | left: borderSide,
114 | bottom: isFirst ? borderSide : BorderSide.none,
115 | )),
116 | child: _wrapGesture(context, Center(
117 | child: Text(
118 | item.title,
119 | textAlign: TextAlign.center,
120 | style: TextStyle(fontSize: 14, color: Colors.white),
121 | ),
122 | ), item)
123 | ),
124 | );
125 | }
126 | // 封装点击跳转逻辑
127 | _wrapGesture(BuildContext context, Widget widget, CommonModel model) {
128 | return GestureDetector(
129 | child: widget,
130 | onTap: (){
131 | Navigator.push(context, MaterialPageRoute(builder: (context)=>(
132 | WebView(url: model.url, statusBarColor: model.statusBarColor,hideAppBar: model.hideAppBar, title: model.title,)
133 | )));
134 | },
135 | );
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/lib/widgets/loading_container.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_spinkit/flutter_spinkit.dart';
3 |
4 | // 加载进度条组件
5 | class LoadingContainer extends StatelessWidget {
6 | final Widget child;
7 | final bool isLoading;
8 | final bool cover;
9 |
10 | const LoadingContainer(
11 | {Key key,
12 | @required this.isLoading,
13 | this.cover = false,
14 | @required this.child})
15 | : super(key: key);
16 |
17 | @override
18 | Widget build(BuildContext context) {
19 | return !cover? !isLoading ? child : _loadingView :
20 | Stack(
21 | children: [
22 | child, isLoading? _loadingView : null
23 | ],
24 | );
25 | }
26 |
27 | Widget get _loadingView{
28 | return Center(
29 | child: new Column(
30 | mainAxisAlignment: MainAxisAlignment.center,
31 | crossAxisAlignment: CrossAxisAlignment.center,
32 | children: [
33 | // new CircularProgressIndicator(),
34 | new SpinKitPumpingHeart(color: Colors.blue,),
35 | new Padding(padding: const EdgeInsets.only(top: 20.0),
36 | child: new Text(
37 | '拼命加载中...',
38 | style: new TextStyle(fontSize: 12.0, color: Colors.lightBlue),
39 | ),
40 | )
41 | ],
42 | ),
43 | );
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/lib/widgets/local_nav.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_wtrip/model/common_model.dart';
3 | import 'package:flutter_wtrip/widgets/webview.dart';
4 |
5 | class LocalNav extends StatelessWidget {
6 | final List localNavList;
7 |
8 | const LocalNav({Key key, @required this.localNavList}) : super(key: key);
9 |
10 | @override
11 | Widget build(BuildContext context) {
12 | // TODO: implement build
13 | return Container(
14 | height: 64,
15 | decoration: BoxDecoration(
16 | color: Colors.white,
17 | borderRadius: BorderRadius.all(Radius.circular(6))),
18 | child: Padding(
19 | padding: EdgeInsets.all(7),
20 | child: _items(context),
21 | ),
22 | );
23 | }
24 |
25 | _items(BuildContext context) {
26 | if (localNavList.length == 0) return null;
27 |
28 | List items = [];
29 | localNavList.forEach((model) {
30 | items.add(_item(context, model));
31 | });
32 |
33 | return Row(
34 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
35 | children: items,
36 | );
37 | }
38 |
39 | Widget _item(BuildContext context, CommonModel model) {
40 | return GestureDetector(
41 | onTap: (){
42 | Navigator.push(context, MaterialPageRoute(builder: (context)=>(
43 | WebView(url: model.url, statusBarColor: model.statusBarColor,hideAppBar: model.hideAppBar,)
44 | )));
45 | },
46 | child: Column(
47 | children: [
48 | Image.network(
49 | model.icon,
50 | width: 32,
51 | height: 32,
52 | ),
53 | Text(
54 | model.title,
55 | style: TextStyle(fontSize: 12),
56 | )
57 | ],
58 | ),
59 | );
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/lib/widgets/sales_box.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_wtrip/model/common_model.dart';
3 | import 'package:flutter_wtrip/model/sales_box_model.dart';
4 | import 'package:flutter_wtrip/widgets/webview.dart';
5 |
6 | class SalesBox extends StatelessWidget {
7 | final SalesBoxModel salesBox;
8 |
9 | const SalesBox({Key key, @required this.salesBox}) : super(key: key);
10 |
11 | @override
12 | Widget build(BuildContext context) {
13 | // TODO: implement build
14 | return Container(
15 | decoration: BoxDecoration(
16 | color: Colors.white,
17 | borderRadius: BorderRadius.all(Radius.circular(6))),
18 | child: _items(context),
19 | );
20 | }
21 |
22 | _items(BuildContext context) {
23 | List items = [];
24 |
25 | items.add(_doubleItem(
26 | context, salesBox.bigCard1, salesBox.bigCard2, true, true, false));
27 | items.add(_doubleItem(
28 | context, salesBox.smallCard1, salesBox.smallCard2, false, true, false));
29 | items.add(_doubleItem(
30 | context, salesBox.smallCard3, salesBox.smallCard4, false, false, true));
31 |
32 | return Column(
33 | children: [
34 | Container(
35 | height: 44,
36 | margin: EdgeInsets.only(left: 10),
37 | decoration: BoxDecoration(
38 | border: Border(
39 | bottom: BorderSide(width: 1, color: Color(0xffff2f2f2)))),
40 | child: Row(
41 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
42 | children: [
43 | Image.network(
44 | salesBox.icon,
45 | height: 15,
46 | fit: BoxFit.fill,
47 | ),
48 | Container(
49 | padding: EdgeInsets.fromLTRB(10, 1, 7, 1),
50 | margin: EdgeInsets.only(right: 7),
51 | decoration: BoxDecoration(
52 | borderRadius: BorderRadius.circular(12),
53 | gradient: LinearGradient(
54 | colors: [
55 | Color(0xffff4e63),
56 | Color(0xffff6cc9),
57 | ],
58 | begin: Alignment.centerLeft,
59 | end: Alignment.centerRight)),
60 | child: GestureDetector(
61 | onTap: () {},
62 | child: Text(
63 | '获取更多 >',
64 | style: TextStyle(color: Colors.white, fontSize: 12),
65 | ),
66 | ),
67 | ),
68 | ],
69 | ),
70 | ),
71 | Row(
72 | mainAxisAlignment: MainAxisAlignment.center,
73 | children: items.sublist(0, 1),
74 | ),
75 | Row(
76 | mainAxisAlignment: MainAxisAlignment.center,
77 | children: items.sublist(1, 2),
78 | ),
79 | Row(
80 | mainAxisAlignment: MainAxisAlignment.center,
81 | children: items.sublist(2, 3),
82 | ),
83 | ],
84 | );
85 | }
86 |
87 | Widget _doubleItem(BuildContext context, CommonModel leftCard,
88 | CommonModel rightCard, bool big, bool left, bool last) {
89 | return Row(
90 | mainAxisAlignment: MainAxisAlignment.spaceAround,
91 | children: [
92 | _item(context, leftCard, big, true, last),
93 | _item(context, rightCard, big, false, last)
94 | ],
95 | );
96 | }
97 |
98 | Widget _item(
99 | BuildContext context, CommonModel model, bool big, bool left, bool last) {
100 | BorderSide borderSide = BorderSide(width: 0.8, color: Color(0xffff2f2f2));
101 | return GestureDetector(
102 | onTap: () {
103 | Navigator.push(
104 | context,
105 | MaterialPageRoute(
106 | builder: (context) => (WebView(
107 | url: model.url,
108 | statusBarColor: model.statusBarColor,
109 | hideAppBar: model.hideAppBar,
110 | ))));
111 | },
112 | child: Container(
113 | decoration: BoxDecoration(
114 | border: Border(
115 | right: left? BorderSide.none: borderSide,
116 | bottom: last? BorderSide.none: borderSide
117 | )
118 | ),
119 | child: Image.network(
120 | model.icon,
121 | fit: BoxFit.fill,
122 | width: MediaQuery.of(context).size.width / 2- 10, // 获取设备宽度
123 | height: big ? 129 : 80,
124 | ),
125 | ));
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/lib/widgets/search_bar.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | enum SearchBarType { home, normal, homeLight }
4 |
5 | class SearchBar extends StatefulWidget {
6 | final bool enabled;
7 | final bool hideLeft;
8 | final SearchBarType searchBarType;
9 | final String hint;
10 | final String defaultText;
11 | final String city;
12 | final void Function() leftButtonClick;
13 | final void Function() rightButtonClick;
14 | final void Function() speakClick;
15 | final void Function() inputBoxClick;
16 | final ValueChanged onChange;
17 |
18 | const SearchBar(
19 | {Key key,
20 | this.enabled = true,
21 | this.hideLeft,
22 | this.searchBarType = SearchBarType.normal,
23 | this.hint, // 提示文案
24 | this.defaultText,
25 | this.leftButtonClick,
26 | this.rightButtonClick,
27 | this.speakClick,
28 | this.inputBoxClick,
29 | this.city,
30 | this.onChange})
31 | : super(key: key);
32 |
33 | @override
34 | _SearchBarState createState() => _SearchBarState();
35 | }
36 |
37 | class _SearchBarState extends State {
38 | bool showClear = false;
39 | final TextEditingController _controller = TextEditingController();
40 |
41 | @override
42 | void initState() {
43 | // TODO: implement initState
44 | if (widget.defaultText != null) {
45 | _controller.text = widget.defaultText;
46 | }
47 | super.initState();
48 | }
49 |
50 | @override
51 | Widget build(BuildContext context) {
52 | return widget.searchBarType == SearchBarType.normal
53 | ? _genNormalSearch()
54 | : _genHomeSearch();
55 | }
56 |
57 | _genNormalSearch() {
58 | return Container(
59 | child: Row(
60 | children: [
61 | _wrapTap(
62 | Container(
63 | padding: EdgeInsets.fromLTRB(6, 5, 10, 5),
64 | child: widget?.hideLeft ?? false
65 | ? null
66 | : Icon(
67 | Icons.arrow_back,
68 | color: Colors.grey,
69 | size: 26,
70 | ),
71 | ),
72 | widget.leftButtonClick),
73 | Expanded(
74 | flex: 1,
75 | child: _inputBox(),
76 | ),
77 | _wrapTap(
78 | Container(
79 | padding: EdgeInsets.fromLTRB(10, 5, 10, 5),
80 | child: Text(
81 | '搜索',
82 | style: TextStyle(
83 | color: Colors.blue,
84 | fontSize: 17,
85 | ),
86 | ),
87 | ),
88 | widget.rightButtonClick),
89 | ],
90 | ),
91 | );
92 | }
93 |
94 | _genHomeSearch() {
95 | return Container(
96 | child: Row(
97 | children: [
98 | _wrapTap(
99 | Container(
100 | padding: EdgeInsets.fromLTRB(6, 5, 5, 5),
101 | child: Row(
102 | children: [
103 | Text(
104 | widget.city,
105 | style: TextStyle(color: _homeFontColor(), fontSize: 14),
106 | ),
107 | Icon(
108 | Icons.expand_more,
109 | color: _homeFontColor(),
110 | size: 22,
111 | )
112 | ],
113 | ),
114 | ),
115 | widget.leftButtonClick),
116 | Expanded(
117 | flex: 1,
118 | child: _inputBox(),
119 | ),
120 | _wrapTap(
121 | Container(
122 | padding: EdgeInsets.fromLTRB(10, 5, 10, 5),
123 | child: Icon(
124 | Icons.comment,
125 | color: _homeFontColor(),
126 | size: 26,
127 | ),
128 | ),
129 | widget.rightButtonClick),
130 | ],
131 | ),
132 | );
133 | }
134 |
135 | _inputBox() {
136 | Color inputBoxColor;
137 | if (widget.searchBarType == SearchBarType.home) {
138 | inputBoxColor = Colors.white;
139 | } else {
140 | inputBoxColor = Color(int.parse('0xffEDEDED'));
141 | }
142 |
143 | return Container(
144 | height: 30,
145 | padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
146 | decoration: BoxDecoration(
147 | color: inputBoxColor,
148 | borderRadius: BorderRadius.circular(
149 | widget.searchBarType == SearchBarType.normal ? 5 : 15)),
150 | child: Row(
151 | children: [
152 | Icon(
153 | Icons.search,
154 | size: 20,
155 | color: widget.searchBarType == SearchBarType.normal
156 | ? Color(0xffA9A9A9)
157 | : Colors.blue,
158 | ),
159 | Expanded(
160 | flex: 1,
161 | child: widget.searchBarType == SearchBarType.normal
162 | ? TextField(
163 | controller: _controller,
164 | onChanged: _onChanged,
165 | autofocus: false,
166 | style: TextStyle(
167 | fontSize: 15,
168 | color: Colors.black,
169 | fontWeight: FontWeight.w300,
170 | textBaseline: TextBaseline.alphabetic),
171 | decoration: InputDecoration(
172 | contentPadding: EdgeInsets.fromLTRB(5, 0, 5, 0),
173 | border: InputBorder.none,
174 | hintText: widget.hint ?? "",
175 | hintStyle: TextStyle(fontSize: 15),
176 | isDense: true,
177 | ),
178 | )
179 | : _wrapTap(
180 | Container(
181 | child: Text(
182 | widget.defaultText,
183 | style: TextStyle(fontSize: 13, color: Colors.grey),
184 | ),
185 | ),
186 | widget.inputBoxClick),
187 | ),
188 | !showClear
189 | ? _wrapTap(
190 | Icon(
191 | Icons.mic,
192 | size: 20,
193 | color: widget.searchBarType == SearchBarType.normal
194 | ? Colors.blue
195 | : Colors.grey,
196 | ),
197 | widget.speakClick)
198 | : _wrapTap(
199 | Icon(
200 | Icons.clear,
201 | size: 20,
202 | color: Colors.grey,
203 | ), () {
204 | setState(() {
205 | _controller.clear();
206 | });
207 | _onChanged('');
208 | })
209 | ],
210 | ),
211 | );
212 | }
213 |
214 | _onChanged(String text) {
215 | if (text.length > 0) {
216 | setState(() {
217 | showClear = true;
218 | });
219 | } else {
220 | setState(() {
221 | showClear = false;
222 | });
223 | }
224 |
225 | if (widget.onChange != null) {
226 | widget.onChange(text);
227 | }
228 | }
229 |
230 | _wrapTap(Widget child, void Function() callback) {
231 | return GestureDetector(
232 | onTap: () {
233 | if (callback != null) callback();
234 | },
235 | child: child,
236 | );
237 | }
238 |
239 | _homeFontColor() {
240 | return widget.searchBarType == SearchBarType.homeLight
241 | ? Colors.black54
242 | : Colors.white;
243 | }
244 | }
245 |
--------------------------------------------------------------------------------
/lib/widgets/sub_nav.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_wtrip/model/common_model.dart';
3 | import 'package:flutter_wtrip/widgets/webview.dart';
4 |
5 | class SubNav extends StatelessWidget {
6 | final List subNavList;
7 |
8 | const SubNav({Key key, @required this.subNavList}) : super(key: key);
9 |
10 | @override
11 | Widget build(BuildContext context) {
12 | // TODO: implement build
13 | return Container(
14 | // height: 64,
15 | decoration: BoxDecoration(
16 | color: Colors.white,
17 | borderRadius: BorderRadius.all(Radius.circular(6))),
18 | child: Padding(
19 | padding: EdgeInsets.all(7),
20 | child: _items(context),
21 | ),
22 | );
23 | }
24 |
25 | _items(BuildContext context) {
26 | if (subNavList.length == 0) return null;
27 |
28 | List items = [];
29 | subNavList.forEach((model) {
30 | items.add(_item(context, model));
31 | });
32 |
33 | var spec = (items.length / 2 + 0.5).toInt();
34 |
35 | return Column(
36 | children: [
37 | Row(
38 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
39 | children: items.sublist(0, spec),
40 | ),
41 | Padding(
42 | padding: EdgeInsets.only(top: 10),
43 | child: Row(
44 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
45 | children: items.sublist(spec, items.length),
46 | ),
47 | )
48 | ],
49 | );
50 | }
51 |
52 | Widget _item(BuildContext context, CommonModel model) {
53 | return Expanded(
54 | flex: 1,
55 | child: GestureDetector(
56 | onTap: () {
57 | Navigator.push(
58 | context,
59 | MaterialPageRoute(
60 | builder: (context) => (WebView(
61 | url: model.url,
62 | statusBarColor: model.statusBarColor,
63 | hideAppBar: model.hideAppBar,
64 | ))));
65 | },
66 | child: Column(
67 | children: [
68 | Image.network(
69 | model.icon,
70 | width: 18,
71 | height: 18,
72 | ),
73 | Padding(
74 | padding: EdgeInsets.only(top: 3),
75 | child: Text(
76 | model.title,
77 | style: TextStyle(fontSize: 12),
78 | ),
79 | )
80 | ],
81 | ),
82 | ),
83 | );
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/lib/widgets/webview.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';
5 |
6 | const CATCH_URLS = ['m.ctrip.com','m.ctrip.com/html5/','m.ctrip.com/html5','https://m.ctrip.com/webapp'];
7 |
8 | class WebView extends StatefulWidget {
9 | final String url;
10 | final String statusBarColor;
11 | final String title;
12 | final bool hideAppBar;
13 | final bool backForbid; // 禁止返回
14 |
15 |
16 |
17 | WebView(
18 | {Key key,
19 | this.url,
20 | this.statusBarColor,
21 | this.title,
22 | this.hideAppBar,
23 | this.backForbid = false});
24 |
25 | @override
26 | _WebViewState createState() => _WebViewState();
27 | }
28 |
29 | class _WebViewState extends State {
30 | final flutterWebviewPlugin = new FlutterWebviewPlugin();
31 |
32 | StreamSubscription _onUrlChangeListener;
33 | StreamSubscription _onStateChangeListener;
34 |
35 | bool exiting = false;
36 |
37 | @override
38 | void initState() {
39 | // TODO: implement initState
40 | super.initState();
41 | flutterWebviewPlugin.close();
42 | _onUrlChangeListener =
43 | flutterWebviewPlugin.onUrlChanged.listen((String url) {});
44 | _onStateChangeListener =
45 | flutterWebviewPlugin.onStateChanged.listen((WebViewStateChanged state) {
46 | switch (state.type) {
47 | case WebViewState.startLoad:
48 | if(_isToMain(state.url) && !exiting) {
49 | if(widget.backForbid) {
50 | flutterWebviewPlugin.launch(widget.url);
51 | } else {
52 | Navigator.pop(context);
53 | exiting = true;
54 | }
55 | }
56 | break;
57 | default:
58 | break;
59 | }
60 | });
61 | }
62 |
63 | _isToMain(String url){
64 | bool contain = false;
65 | for(final value in CATCH_URLS) {
66 | if(url?.endsWith(value)??false) {
67 | contain = true;
68 | break;
69 | }
70 | }
71 |
72 | return contain;
73 | }
74 |
75 | @override
76 | void dispose() {
77 | // TODO: implement dispose
78 | _onUrlChangeListener.cancel();
79 | _onStateChangeListener.cancel();
80 | flutterWebviewPlugin.dispose();
81 | super.dispose();
82 | }
83 |
84 | @override
85 | Widget build(BuildContext context) {
86 |
87 | String statusBarColor = widget.statusBarColor ?? 'ffffff';
88 | // TODO: implement build
89 | Color backButtonColor;
90 | if(statusBarColor == 'ffffff') {
91 | backButtonColor = Colors.black;
92 | } else {
93 | backButtonColor = Colors.white;
94 | }
95 | return Scaffold(
96 | body: Column(
97 | children: [
98 | _appBar(Color(int.parse('0xff'+statusBarColor)), backButtonColor), // 将字符串颜色转成16进制颜色
99 | Expanded(
100 | child: WebviewScaffold(url: widget.url,
101 | withZoom: true,
102 | hidden: true,
103 | initialChild: Container(
104 | color: Colors.white,
105 | child: Center(
106 | child: new Column(
107 | mainAxisAlignment: MainAxisAlignment.center,
108 | crossAxisAlignment: CrossAxisAlignment.center,
109 | children: [
110 | new CircularProgressIndicator(),
111 | new Padding(padding: const EdgeInsets.only(top: 20.0),
112 | child: new Text(
113 | '加载中',
114 | style: new TextStyle(fontSize: 12.0, color: Colors.lightBlue),
115 | ),
116 | )
117 | ],
118 | ),
119 | ),
120 | ),
121 | ),
122 | )
123 | ],
124 | ),
125 | );
126 | }
127 |
128 | _appBar(Color backgroundColor, Color backButtonColor) {
129 | if(widget.hideAppBar??false) {
130 | return Container(
131 | height: 30,
132 | color: backgroundColor,
133 | );
134 | }
135 | return Container(
136 | color: backgroundColor,
137 | padding: EdgeInsets.fromLTRB(0, 40, 0, 10),
138 | child: FractionallySizedBox(
139 | widthFactor: 1,
140 | child: Stack(
141 | children: [
142 | GestureDetector(
143 | onTap: (){
144 | Navigator.pop(context);
145 | },
146 | child: Container(
147 | margin: EdgeInsets.only(left: 10),
148 | child: Icon(
149 | Icons.arrow_back,
150 | color: backButtonColor,
151 | size: 26,
152 | ),
153 | ),
154 | ),
155 | Positioned(
156 | left: 0,
157 | right: 0,
158 | child: Center(
159 | child: Text(
160 | widget.title ?? "",
161 | style: TextStyle(color: backButtonColor, fontSize: 20),
162 | ),
163 | ))
164 | ],
165 | ),
166 | ));
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://dart.dev/tools/pub/glossary#lockfile
3 | packages:
4 | analyzer:
5 | dependency: transitive
6 | description:
7 | name: analyzer
8 | url: "https://pub.flutter-io.cn"
9 | source: hosted
10 | version: "0.36.4"
11 | archive:
12 | dependency: transitive
13 | description:
14 | name: archive
15 | url: "https://pub.flutter-io.cn"
16 | source: hosted
17 | version: "2.0.11"
18 | args:
19 | dependency: transitive
20 | description:
21 | name: args
22 | url: "https://pub.flutter-io.cn"
23 | source: hosted
24 | version: "1.5.2"
25 | async:
26 | dependency: transitive
27 | description:
28 | name: async
29 | url: "https://pub.flutter-io.cn"
30 | source: hosted
31 | version: "2.4.0"
32 | azlistview:
33 | dependency: "direct main"
34 | description:
35 | name: azlistview
36 | url: "https://pub.flutter-io.cn"
37 | source: hosted
38 | version: "0.1.2"
39 | boolean_selector:
40 | dependency: transitive
41 | description:
42 | name: boolean_selector
43 | url: "https://pub.flutter-io.cn"
44 | source: hosted
45 | version: "1.0.5"
46 | build:
47 | dependency: transitive
48 | description:
49 | name: build
50 | url: "https://pub.flutter-io.cn"
51 | source: hosted
52 | version: "1.1.6"
53 | build_config:
54 | dependency: transitive
55 | description:
56 | name: build_config
57 | url: "https://pub.flutter-io.cn"
58 | source: hosted
59 | version: "0.4.1+1"
60 | build_daemon:
61 | dependency: transitive
62 | description:
63 | name: build_daemon
64 | url: "https://pub.flutter-io.cn"
65 | source: hosted
66 | version: "2.1.4"
67 | build_resolvers:
68 | dependency: transitive
69 | description:
70 | name: build_resolvers
71 | url: "https://pub.flutter-io.cn"
72 | source: hosted
73 | version: "1.2.1"
74 | build_runner:
75 | dependency: transitive
76 | description:
77 | name: build_runner
78 | url: "https://pub.flutter-io.cn"
79 | source: hosted
80 | version: "1.6.9"
81 | build_runner_core:
82 | dependency: transitive
83 | description:
84 | name: build_runner_core
85 | url: "https://pub.flutter-io.cn"
86 | source: hosted
87 | version: "3.1.1"
88 | built_collection:
89 | dependency: transitive
90 | description:
91 | name: built_collection
92 | url: "https://pub.flutter-io.cn"
93 | source: hosted
94 | version: "4.3.2"
95 | built_value:
96 | dependency: transitive
97 | description:
98 | name: built_value
99 | url: "https://pub.flutter-io.cn"
100 | source: hosted
101 | version: "7.1.0"
102 | cached_network_image:
103 | dependency: "direct main"
104 | description:
105 | name: cached_network_image
106 | url: "https://pub.flutter-io.cn"
107 | source: hosted
108 | version: "2.2.0+1"
109 | charcode:
110 | dependency: transitive
111 | description:
112 | name: charcode
113 | url: "https://pub.flutter-io.cn"
114 | source: hosted
115 | version: "1.1.2"
116 | checked_yaml:
117 | dependency: transitive
118 | description:
119 | name: checked_yaml
120 | url: "https://pub.flutter-io.cn"
121 | source: hosted
122 | version: "1.0.2"
123 | clock:
124 | dependency: transitive
125 | description:
126 | name: clock
127 | url: "https://pub.flutter-io.cn"
128 | source: hosted
129 | version: "1.0.1"
130 | code_builder:
131 | dependency: transitive
132 | description:
133 | name: code_builder
134 | url: "https://pub.flutter-io.cn"
135 | source: hosted
136 | version: "3.3.0"
137 | collection:
138 | dependency: transitive
139 | description:
140 | name: collection
141 | url: "https://pub.flutter-io.cn"
142 | source: hosted
143 | version: "1.14.11"
144 | convert:
145 | dependency: transitive
146 | description:
147 | name: convert
148 | url: "https://pub.flutter-io.cn"
149 | source: hosted
150 | version: "2.1.1"
151 | crypto:
152 | dependency: transitive
153 | description:
154 | name: crypto
155 | url: "https://pub.flutter-io.cn"
156 | source: hosted
157 | version: "2.1.3"
158 | csslib:
159 | dependency: transitive
160 | description:
161 | name: csslib
162 | url: "https://pub.flutter-io.cn"
163 | source: hosted
164 | version: "0.16.1"
165 | cupertino_icons:
166 | dependency: "direct main"
167 | description:
168 | name: cupertino_icons
169 | url: "https://pub.flutter-io.cn"
170 | source: hosted
171 | version: "0.1.3"
172 | dart_style:
173 | dependency: transitive
174 | description:
175 | name: dart_style
176 | url: "https://pub.flutter-io.cn"
177 | source: hosted
178 | version: "1.2.9"
179 | dio:
180 | dependency: "direct main"
181 | description:
182 | name: dio
183 | url: "https://pub.flutter-io.cn"
184 | source: hosted
185 | version: "3.0.9"
186 | event_bus:
187 | dependency: "direct main"
188 | description:
189 | name: event_bus
190 | url: "https://pub.flutter-io.cn"
191 | source: hosted
192 | version: "1.1.1"
193 | file:
194 | dependency: transitive
195 | description:
196 | name: file
197 | url: "https://pub.flutter-io.cn"
198 | source: hosted
199 | version: "5.1.0"
200 | fixnum:
201 | dependency: transitive
202 | description:
203 | name: fixnum
204 | url: "https://pub.flutter-io.cn"
205 | source: hosted
206 | version: "0.10.11"
207 | flutter:
208 | dependency: "direct main"
209 | description: flutter
210 | source: sdk
211 | version: "0.0.0"
212 | flutter_cache_manager:
213 | dependency: transitive
214 | description:
215 | name: flutter_cache_manager
216 | url: "https://pub.flutter-io.cn"
217 | source: hosted
218 | version: "1.2.2"
219 | flutter_page_indicator:
220 | dependency: transitive
221 | description:
222 | name: flutter_page_indicator
223 | url: "https://pub.flutter-io.cn"
224 | source: hosted
225 | version: "0.0.3"
226 | flutter_spinkit:
227 | dependency: "direct main"
228 | description:
229 | name: flutter_spinkit
230 | url: "https://pub.flutter-io.cn"
231 | source: hosted
232 | version: "4.1.2+1"
233 | flutter_staggered_grid_view:
234 | dependency: "direct main"
235 | description:
236 | name: flutter_staggered_grid_view
237 | url: "https://pub.flutter-io.cn"
238 | source: hosted
239 | version: "0.3.0"
240 | flutter_swiper:
241 | dependency: "direct main"
242 | description:
243 | name: flutter_swiper
244 | url: "https://pub.flutter-io.cn"
245 | source: hosted
246 | version: "1.1.6"
247 | flutter_test:
248 | dependency: "direct dev"
249 | description: flutter
250 | source: sdk
251 | version: "0.0.0"
252 | flutter_webview_plugin:
253 | dependency: "direct main"
254 | description:
255 | name: flutter_webview_plugin
256 | url: "https://pub.flutter-io.cn"
257 | source: hosted
258 | version: "0.3.11"
259 | front_end:
260 | dependency: transitive
261 | description:
262 | name: front_end
263 | url: "https://pub.flutter-io.cn"
264 | source: hosted
265 | version: "0.1.19"
266 | glob:
267 | dependency: transitive
268 | description:
269 | name: glob
270 | url: "https://pub.flutter-io.cn"
271 | source: hosted
272 | version: "1.2.0"
273 | graphs:
274 | dependency: transitive
275 | description:
276 | name: graphs
277 | url: "https://pub.flutter-io.cn"
278 | source: hosted
279 | version: "0.2.0"
280 | html:
281 | dependency: transitive
282 | description:
283 | name: html
284 | url: "https://pub.flutter-io.cn"
285 | source: hosted
286 | version: "0.14.0+3"
287 | http:
288 | dependency: "direct main"
289 | description:
290 | name: http
291 | url: "https://pub.flutter-io.cn"
292 | source: hosted
293 | version: "0.12.1"
294 | http_multi_server:
295 | dependency: transitive
296 | description:
297 | name: http_multi_server
298 | url: "https://pub.flutter-io.cn"
299 | source: hosted
300 | version: "2.2.0"
301 | http_parser:
302 | dependency: transitive
303 | description:
304 | name: http_parser
305 | url: "https://pub.flutter-io.cn"
306 | source: hosted
307 | version: "3.1.4"
308 | image:
309 | dependency: transitive
310 | description:
311 | name: image
312 | url: "https://pub.flutter-io.cn"
313 | source: hosted
314 | version: "2.1.4"
315 | intl:
316 | dependency: transitive
317 | description:
318 | name: intl
319 | url: "https://pub.flutter-io.cn"
320 | source: hosted
321 | version: "0.16.1"
322 | io:
323 | dependency: transitive
324 | description:
325 | name: io
326 | url: "https://pub.flutter-io.cn"
327 | source: hosted
328 | version: "0.3.4"
329 | js:
330 | dependency: transitive
331 | description:
332 | name: js
333 | url: "https://pub.flutter-io.cn"
334 | source: hosted
335 | version: "0.6.2"
336 | json_annotation:
337 | dependency: transitive
338 | description:
339 | name: json_annotation
340 | url: "https://pub.flutter-io.cn"
341 | source: hosted
342 | version: "2.3.0"
343 | json_model:
344 | dependency: "direct main"
345 | description:
346 | name: json_model
347 | url: "https://pub.flutter-io.cn"
348 | source: hosted
349 | version: "0.0.2"
350 | json_serializable:
351 | dependency: transitive
352 | description:
353 | name: json_serializable
354 | url: "https://pub.flutter-io.cn"
355 | source: hosted
356 | version: "2.3.0"
357 | kernel:
358 | dependency: transitive
359 | description:
360 | name: kernel
361 | url: "https://pub.flutter-io.cn"
362 | source: hosted
363 | version: "0.3.19"
364 | logging:
365 | dependency: transitive
366 | description:
367 | name: logging
368 | url: "https://pub.flutter-io.cn"
369 | source: hosted
370 | version: "0.11.4"
371 | lpinyin:
372 | dependency: "direct main"
373 | description:
374 | name: lpinyin
375 | url: "https://pub.flutter-io.cn"
376 | source: hosted
377 | version: "1.0.9"
378 | matcher:
379 | dependency: transitive
380 | description:
381 | name: matcher
382 | url: "https://pub.flutter-io.cn"
383 | source: hosted
384 | version: "0.12.6"
385 | meta:
386 | dependency: transitive
387 | description:
388 | name: meta
389 | url: "https://pub.flutter-io.cn"
390 | source: hosted
391 | version: "1.1.8"
392 | mime:
393 | dependency: transitive
394 | description:
395 | name: mime
396 | url: "https://pub.flutter-io.cn"
397 | source: hosted
398 | version: "0.9.6+3"
399 | node_interop:
400 | dependency: transitive
401 | description:
402 | name: node_interop
403 | url: "https://pub.flutter-io.cn"
404 | source: hosted
405 | version: "1.1.1"
406 | node_io:
407 | dependency: transitive
408 | description:
409 | name: node_io
410 | url: "https://pub.flutter-io.cn"
411 | source: hosted
412 | version: "1.1.1"
413 | package_config:
414 | dependency: transitive
415 | description:
416 | name: package_config
417 | url: "https://pub.flutter-io.cn"
418 | source: hosted
419 | version: "1.9.3"
420 | package_info:
421 | dependency: "direct main"
422 | description:
423 | name: package_info
424 | url: "https://pub.flutter-io.cn"
425 | source: hosted
426 | version: "0.4.0+18"
427 | package_resolver:
428 | dependency: transitive
429 | description:
430 | name: package_resolver
431 | url: "https://pub.flutter-io.cn"
432 | source: hosted
433 | version: "1.0.10"
434 | path:
435 | dependency: transitive
436 | description:
437 | name: path
438 | url: "https://pub.flutter-io.cn"
439 | source: hosted
440 | version: "1.6.4"
441 | path_provider:
442 | dependency: transitive
443 | description:
444 | name: path_provider
445 | url: "https://pub.flutter-io.cn"
446 | source: hosted
447 | version: "1.6.8"
448 | path_provider_macos:
449 | dependency: transitive
450 | description:
451 | name: path_provider_macos
452 | url: "https://pub.flutter-io.cn"
453 | source: hosted
454 | version: "0.0.4+2"
455 | path_provider_platform_interface:
456 | dependency: transitive
457 | description:
458 | name: path_provider_platform_interface
459 | url: "https://pub.flutter-io.cn"
460 | source: hosted
461 | version: "1.0.2"
462 | pedantic:
463 | dependency: transitive
464 | description:
465 | name: pedantic
466 | url: "https://pub.flutter-io.cn"
467 | source: hosted
468 | version: "1.8.0+1"
469 | petitparser:
470 | dependency: transitive
471 | description:
472 | name: petitparser
473 | url: "https://pub.flutter-io.cn"
474 | source: hosted
475 | version: "2.4.0"
476 | platform:
477 | dependency: transitive
478 | description:
479 | name: platform
480 | url: "https://pub.flutter-io.cn"
481 | source: hosted
482 | version: "2.2.1"
483 | plugin_platform_interface:
484 | dependency: transitive
485 | description:
486 | name: plugin_platform_interface
487 | url: "https://pub.flutter-io.cn"
488 | source: hosted
489 | version: "1.0.2"
490 | pool:
491 | dependency: transitive
492 | description:
493 | name: pool
494 | url: "https://pub.flutter-io.cn"
495 | source: hosted
496 | version: "1.4.0"
497 | provider:
498 | dependency: "direct main"
499 | description:
500 | name: provider
501 | url: "https://pub.flutter-io.cn"
502 | source: hosted
503 | version: "3.2.0"
504 | pub_semver:
505 | dependency: transitive
506 | description:
507 | name: pub_semver
508 | url: "https://pub.flutter-io.cn"
509 | source: hosted
510 | version: "1.4.4"
511 | pubspec_parse:
512 | dependency: transitive
513 | description:
514 | name: pubspec_parse
515 | url: "https://pub.flutter-io.cn"
516 | source: hosted
517 | version: "0.1.5"
518 | quiver:
519 | dependency: transitive
520 | description:
521 | name: quiver
522 | url: "https://pub.flutter-io.cn"
523 | source: hosted
524 | version: "2.0.5"
525 | rxdart:
526 | dependency: transitive
527 | description:
528 | name: rxdart
529 | url: "https://pub.flutter-io.cn"
530 | source: hosted
531 | version: "0.24.1"
532 | shelf:
533 | dependency: transitive
534 | description:
535 | name: shelf
536 | url: "https://pub.flutter-io.cn"
537 | source: hosted
538 | version: "0.7.7"
539 | shelf_web_socket:
540 | dependency: transitive
541 | description:
542 | name: shelf_web_socket
543 | url: "https://pub.flutter-io.cn"
544 | source: hosted
545 | version: "0.2.3"
546 | sky_engine:
547 | dependency: transitive
548 | description: flutter
549 | source: sdk
550 | version: "0.0.99"
551 | source_gen:
552 | dependency: transitive
553 | description:
554 | name: source_gen
555 | url: "https://pub.flutter-io.cn"
556 | source: hosted
557 | version: "0.9.4+4"
558 | source_span:
559 | dependency: transitive
560 | description:
561 | name: source_span
562 | url: "https://pub.flutter-io.cn"
563 | source: hosted
564 | version: "1.5.5"
565 | sqflite:
566 | dependency: transitive
567 | description:
568 | name: sqflite
569 | url: "https://pub.flutter-io.cn"
570 | source: hosted
571 | version: "1.3.0+1"
572 | sqflite_common:
573 | dependency: transitive
574 | description:
575 | name: sqflite_common
576 | url: "https://pub.flutter-io.cn"
577 | source: hosted
578 | version: "1.0.1"
579 | stack_trace:
580 | dependency: transitive
581 | description:
582 | name: stack_trace
583 | url: "https://pub.flutter-io.cn"
584 | source: hosted
585 | version: "1.9.3"
586 | stream_channel:
587 | dependency: transitive
588 | description:
589 | name: stream_channel
590 | url: "https://pub.flutter-io.cn"
591 | source: hosted
592 | version: "2.0.0"
593 | stream_transform:
594 | dependency: transitive
595 | description:
596 | name: stream_transform
597 | url: "https://pub.flutter-io.cn"
598 | source: hosted
599 | version: "0.0.20"
600 | string_scanner:
601 | dependency: transitive
602 | description:
603 | name: string_scanner
604 | url: "https://pub.flutter-io.cn"
605 | source: hosted
606 | version: "1.0.5"
607 | synchronized:
608 | dependency: transitive
609 | description:
610 | name: synchronized
611 | url: "https://pub.flutter-io.cn"
612 | source: hosted
613 | version: "2.2.0"
614 | term_glyph:
615 | dependency: transitive
616 | description:
617 | name: term_glyph
618 | url: "https://pub.flutter-io.cn"
619 | source: hosted
620 | version: "1.1.0"
621 | test_api:
622 | dependency: transitive
623 | description:
624 | name: test_api
625 | url: "https://pub.flutter-io.cn"
626 | source: hosted
627 | version: "0.2.11"
628 | timing:
629 | dependency: transitive
630 | description:
631 | name: timing
632 | url: "https://pub.flutter-io.cn"
633 | source: hosted
634 | version: "0.1.1+2"
635 | transformer_page_view:
636 | dependency: transitive
637 | description:
638 | name: transformer_page_view
639 | url: "https://pub.flutter-io.cn"
640 | source: hosted
641 | version: "0.1.6"
642 | typed_data:
643 | dependency: transitive
644 | description:
645 | name: typed_data
646 | url: "https://pub.flutter-io.cn"
647 | source: hosted
648 | version: "1.1.6"
649 | uuid:
650 | dependency: transitive
651 | description:
652 | name: uuid
653 | url: "https://pub.flutter-io.cn"
654 | source: hosted
655 | version: "2.0.4"
656 | vector_math:
657 | dependency: transitive
658 | description:
659 | name: vector_math
660 | url: "https://pub.flutter-io.cn"
661 | source: hosted
662 | version: "2.0.8"
663 | watcher:
664 | dependency: transitive
665 | description:
666 | name: watcher
667 | url: "https://pub.flutter-io.cn"
668 | source: hosted
669 | version: "0.9.7+15"
670 | web_socket_channel:
671 | dependency: transitive
672 | description:
673 | name: web_socket_channel
674 | url: "https://pub.flutter-io.cn"
675 | source: hosted
676 | version: "1.1.0"
677 | xml:
678 | dependency: transitive
679 | description:
680 | name: xml
681 | url: "https://pub.flutter-io.cn"
682 | source: hosted
683 | version: "3.5.0"
684 | yaml:
685 | dependency: transitive
686 | description:
687 | name: yaml
688 | url: "https://pub.flutter-io.cn"
689 | source: hosted
690 | version: "2.2.1"
691 | sdks:
692 | dart: ">=2.7.0 <3.0.0"
693 | flutter: ">=1.12.13+hotfix.5 <2.0.0"
694 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: flutter_wtrip
2 | description: A new Flutter application.
3 |
4 | # The following defines the version and build number for your application.
5 | # A version number is three numbers separated by dots, like 1.2.43
6 | # followed by an optional build number separated by a +.
7 | # Both the version and the builder number may be overridden in flutter
8 | # build by specifying --build-name and --build-number, respectively.
9 | # In Android, build-name is used as versionName while build-number used as versionCode.
10 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning
11 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
12 | # Read more about iOS versioning at
13 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
14 | version: 1.0.0+1
15 |
16 | environment:
17 | sdk: ">=2.1.0 <3.0.0"
18 |
19 | dependencies:
20 | flutter:
21 | sdk: flutter
22 |
23 | # The following adds the Cupertino Icons font to your application.
24 | # Use with the CupertinoIcons class for iOS style icons.
25 | cupertino_icons: ^0.1.2
26 | flutter_swiper: ^1.1.6
27 | http: ^0.12.0+4
28 | flutter_webview_plugin: ^0.3.10+1
29 | flutter_staggered_grid_view: ^0.3.0
30 | azlistview: ^0.1.2
31 | lpinyin: ^1.0.8
32 | package_info: ^0.4.0+18
33 | dio: ^3.0.3
34 | cached_network_image: ^2.2.0+1
35 | event_bus: ^1.1.1
36 | flutter_spinkit: ^4.1.2+1
37 | json_model: ^0.0.2
38 | provider: ^3.0.0
39 | dev_dependencies:
40 | flutter_test:
41 | sdk: flutter
42 |
43 |
44 | # For information on the generic Dart part of this file, see the
45 | # following page: https://dart.dev/tools/pub/pubspec
46 |
47 | # The following section is specific to Flutter.
48 | flutter:
49 |
50 | # The following line ensures that the Material Icons font is
51 | # included with your application, so that you can use the icons in
52 | # the material Icons class.
53 | uses-material-design: true
54 |
55 | # To add assets to your application, add an assets section, like this:
56 | assets:
57 | - images/launch_image.png
58 | - assets/data/cities.json
59 |
60 | # An image asset can refer to one or more resolution-specific "variants", see
61 | # https://flutter.dev/assets-and-images/#resolution-aware.
62 |
63 | # For details regarding adding assets from package dependencies, see
64 | # https://flutter.dev/assets-and-images/#from-packages
65 |
66 | # To add custom fonts to your application, add a fonts section here,
67 | # in this "flutter" section. Each entry in this list should have a
68 | # "family" key with the font family name, and a "fonts" key with a
69 | # list giving the asset and other descriptors for the font. For
70 | # example:
71 | # fonts:
72 | # - family: Schyler
73 | # fonts:
74 | # - asset: fonts/Schyler-Regular.ttf
75 | # - asset: fonts/Schyler-Italic.ttf
76 | # style: italic
77 | # - family: Trajan Pro
78 | # fonts:
79 | # - asset: fonts/TrajanPro.ttf
80 | # - asset: fonts/TrajanPro_Bold.ttf
81 | # weight: 700
82 | #
83 | # For details regarding fonts from package dependencies,
84 | # see https://flutter.dev/custom-fonts/#from-packages
--------------------------------------------------------------------------------
/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter widget test.
2 | //
3 | // To perform an interaction with a widget in your test, use the WidgetTester
4 | // utility that Flutter provides. For example, you can send tap and scroll
5 | // gestures. You can also use WidgetTester to find child widgets in the widget
6 | // tree, read text, and verify that the values of widget properties are correct.
7 |
8 | import 'package:flutter/material.dart';
9 | import 'package:flutter_test/flutter_test.dart';
10 |
11 | import 'package:flutter_wtrip/main.dart';
12 |
13 | void main() {
14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async {
15 | // Build our app and trigger a frame.
16 | await tester.pumpWidget(MyApp());
17 |
18 | // Verify that our counter starts at 0.
19 | expect(find.text('0'), findsOneWidget);
20 | expect(find.text('1'), findsNothing);
21 |
22 | // Tap the '+' icon and trigger a frame.
23 | await tester.tap(find.byIcon(Icons.add));
24 | await tester.pump();
25 |
26 | // Verify that our counter has incremented.
27 | expect(find.text('0'), findsNothing);
28 | expect(find.text('1'), findsOneWidget);
29 | });
30 | }
31 |
--------------------------------------------------------------------------------