tum преди 2 години
родител
ревизия
2d82f9b79a

+ 1 - 0
ios/App/Podfile

@@ -17,6 +17,7 @@ def capacitor_pods
17 17
   pod 'CapacitorKeyboard', :path => '../../node_modules/@capacitor/keyboard'
18 18
   pod 'CapacitorPreferences', :path => '../../node_modules/@capacitor/preferences'
19 19
   pod 'CapacitorPushNotifications', :path => '../../node_modules/@capacitor/push-notifications'
20
+  pod 'CapacitorShare', :path => '../../node_modules/@capacitor/share'
20 21
   pod 'CapacitorStatusBar', :path => '../../node_modules/@capacitor/status-bar'
21 22
   pod 'CodetrixStudioCapacitorGoogleAuth', :path => '../../node_modules/@codetrix-studio/capacitor-google-auth'
22 23
 end

+ 7 - 1
ios/App/Podfile.lock

@@ -22,6 +22,8 @@ PODS:
22 22
     - Capacitor
23 23
   - CapacitorPushNotifications (5.1.0):
24 24
     - Capacitor
25
+  - CapacitorShare (5.0.6):
26
+    - Capacitor
25 27
   - CapacitorStatusBar (5.0.6):
26 28
     - Capacitor
27 29
   - CodetrixStudioCapacitorGoogleAuth (0.0.1):
@@ -105,6 +107,7 @@ DEPENDENCIES:
105 107
   - "CapacitorKeyboard (from `../../node_modules/@capacitor/keyboard`)"
106 108
   - "CapacitorPreferences (from `../../node_modules/@capacitor/preferences`)"
107 109
   - "CapacitorPushNotifications (from `../../node_modules/@capacitor/push-notifications`)"
110
+  - "CapacitorShare (from `../../node_modules/@capacitor/share`)"
108 111
   - "CapacitorStatusBar (from `../../node_modules/@capacitor/status-bar`)"
109 112
   - "CodetrixStudioCapacitorGoogleAuth (from `../../node_modules/@codetrix-studio/capacitor-google-auth`)"
110 113
   - Firebase/Messaging
@@ -146,6 +149,8 @@ EXTERNAL SOURCES:
146 149
     :path: "../../node_modules/@capacitor/preferences"
147 150
   CapacitorPushNotifications:
148 151
     :path: "../../node_modules/@capacitor/push-notifications"
152
+  CapacitorShare:
153
+    :path: "../../node_modules/@capacitor/share"
149 154
   CapacitorStatusBar:
150 155
     :path: "../../node_modules/@capacitor/status-bar"
151 156
   CodetrixStudioCapacitorGoogleAuth:
@@ -161,6 +166,7 @@ SPEC CHECKSUMS:
161 166
   CapacitorKeyboard: b978154b024a5f65e044908e37d15b7de58b9d12
162 167
   CapacitorPreferences: f03954bcb0ff09c792909e46bff88e3183c16b10
163 168
   CapacitorPushNotifications: b31e326c6e4eb216a622041d6ca21a973f34943f
169
+  CapacitorShare: cd41743331cb71d217c029de54b681cbd91e0fcc
164 170
   CapacitorStatusBar: 565c0a1ebd79bb40d797606a8992b4a105885309
165 171
   CodetrixStudioCapacitorGoogleAuth: fcce058390347c1ce5d8ac4764bdf1f5c1ee233b
166 172
   FBAEMKit: af2972f39bb0f3f7c45998f435b007833c32ffb2
@@ -180,6 +186,6 @@ SPEC CHECKSUMS:
180 186
   nanopb: d4d75c12cd1316f4a64e3c6963f879ecd4b5e0d5
181 187
   PromisesObjC: c50d2056b5253dadbd6c2bea79b0674bd5a52fa4
182 188
 
183
-PODFILE CHECKSUM: 13a486d397817692d7e7744cf0c6e77b81469655
189
+PODFILE CHECKSUM: b5088696bfe6f308f08046e80f0b75910bd6bc77
184 190
 
185 191
 COCOAPODS: 1.12.1

+ 57 - 17
package-lock.json

@@ -16,14 +16,17 @@
16 16
         "@capacitor/keyboard": "5.0.6",
17 17
         "@capacitor/preferences": "^5.0.6",
18 18
         "@capacitor/push-notifications": "^5.1.0",
19
+        "@capacitor/share": "^5.0.6",
19 20
         "@capacitor/status-bar": "5.0.6",
20 21
         "@codetrix-studio/capacitor-google-auth": "^3.3.4",
21
-        "@ionic/vue": "^7.0.0",
22
-        "@ionic/vue-router": "^7.0.0",
22
+        "@ionic/vue": "^7.5.4",
23
+        "@ionic/vue-router": "^7.5.4",
23 24
         "@vimeo/player": "^2.20.1",
24 25
         "axios": "^1.6.0",
25 26
         "ionicons": "^7.0.0",
27
+        "marked": "^10.0.0",
26 28
         "moment": "^2.29.4",
29
+        "swiper": "^11.0.3",
27 30
         "vue": "^3.2.45",
28 31
         "vue-core-video-player": "^0.2.0",
29 32
         "vue-router": "^4.1.6",
@@ -1919,6 +1922,14 @@
1919 1922
         "@capacitor/core": "^5.0.0"
1920 1923
       }
1921 1924
     },
1925
+    "node_modules/@capacitor/share": {
1926
+      "version": "5.0.6",
1927
+      "resolved": "https://registry.npmjs.org/@capacitor/share/-/share-5.0.6.tgz",
1928
+      "integrity": "sha512-/dVOW7kcuuD7hWB9Z1drArIpEk+5JCoMnMrAs2c7CLp3q5PeaXNJjTkGr6RJ1OtMhsHyXc6DAFwQ4frFkmZsgw==",
1929
+      "peerDependencies": {
1930
+        "@capacitor/core": "^5.0.0"
1931
+      }
1932
+    },
1922 1933
     "node_modules/@capacitor/status-bar": {
1923 1934
       "version": "5.0.6",
1924 1935
       "resolved": "https://registry.npmjs.org/@capacitor/status-bar/-/status-bar-5.0.6.tgz",
@@ -2467,11 +2478,11 @@
2467 2478
       }
2468 2479
     },
2469 2480
     "node_modules/@ionic/core": {
2470
-      "version": "7.5.1",
2471
-      "resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.5.1.tgz",
2472
-      "integrity": "sha512-BnWehjZ3IVGPFLdOZV151VhLsyXzr8d2mIVULqUXil5NHgQf039ScppI0kfrCS+M3zMdqeTmlIK/nq6M+kcQaQ==",
2481
+      "version": "7.5.4",
2482
+      "resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.5.4.tgz",
2483
+      "integrity": "sha512-rZbKlcVucRTDOK2Woh4CWPePlsXiUt3G9dCUniduKD7WeiuAk0GzfmoM3WXBvcUpkVTUIOrvKHaqd3JJSWEIzg==",
2473 2484
       "dependencies": {
2474
-        "@stencil/core": "^4.5.0",
2485
+        "@stencil/core": "^4.7.1",
2475 2486
         "ionicons": "^7.2.1",
2476 2487
         "tslib": "^2.1.0"
2477 2488
       }
@@ -2637,20 +2648,20 @@
2637 2648
       }
2638 2649
     },
2639 2650
     "node_modules/@ionic/vue": {
2640
-      "version": "7.5.1",
2641
-      "resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-7.5.1.tgz",
2642
-      "integrity": "sha512-nA32DFFOJV5vF60xiz6Df/aE7iPEfCZXMAt+pSJYSYQuoMN6qD9eJUM4g37DZIWtRJdzlRBSZQvGEdPR41CyYg==",
2651
+      "version": "7.5.4",
2652
+      "resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-7.5.4.tgz",
2653
+      "integrity": "sha512-DzhD78Yt7Xn9c4iNbWBm9lskx0CBYgIsZwmGJGWFpQAQHJrIRb6M8ozXrIyyBgsMlkw/9dGpmGaauH4pbmwJyw==",
2643 2654
       "dependencies": {
2644
-        "@ionic/core": "7.5.1",
2655
+        "@ionic/core": "7.5.4",
2645 2656
         "ionicons": "^7.0.0"
2646 2657
       }
2647 2658
     },
2648 2659
     "node_modules/@ionic/vue-router": {
2649
-      "version": "7.5.1",
2650
-      "resolved": "https://registry.npmjs.org/@ionic/vue-router/-/vue-router-7.5.1.tgz",
2651
-      "integrity": "sha512-gXITuq8FUzOSovPgm0AmM49TBJbirdAsLU7MiTmBFoNbkjIT+DRP3oVkp/AbI/CbgctH3j889VjCmYxqwPvH0A==",
2660
+      "version": "7.5.4",
2661
+      "resolved": "https://registry.npmjs.org/@ionic/vue-router/-/vue-router-7.5.4.tgz",
2662
+      "integrity": "sha512-hA8t3KFVMn3kBr4REH0yYQ+UZsk4IN7IvtrDN4OEt3/QqtZ5nAZad+3KZDP5dHJ+ITZ6wdmspN3BlAySzykMng==",
2652 2663
       "dependencies": {
2653
-        "@ionic/vue": "7.5.1"
2664
+        "@ionic/vue": "7.5.4"
2654 2665
       }
2655 2666
     },
2656 2667
     "node_modules/@jest/schemas": {
@@ -2768,9 +2779,9 @@
2768 2779
       "dev": true
2769 2780
     },
2770 2781
     "node_modules/@stencil/core": {
2771
-      "version": "4.6.0",
2772
-      "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.6.0.tgz",
2773
-      "integrity": "sha512-5Y6/fP28aspPDRB+Tz5GuB1jRVGuUEk5/rnyE8ACGcuzEOG+zR0A/IHRJSU3XmCxUlVDKfKoO6St5W84oUCVMA==",
2782
+      "version": "4.7.1",
2783
+      "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.7.1.tgz",
2784
+      "integrity": "sha512-KDWA/3qDiABA5LqtHCmTVwORuzgu/YdC0FpSBwVmwlw6K8jUjbTA5JB6Q03da2F+EQlDHOVgbW0TNtHCm54uXQ==",
2774 2785
       "bin": {
2775 2786
         "stencil": "bin/stencil"
2776 2787
       },
@@ -7261,6 +7272,17 @@
7261 7272
         "node": ">=12"
7262 7273
       }
7263 7274
     },
7275
+    "node_modules/marked": {
7276
+      "version": "10.0.0",
7277
+      "resolved": "https://registry.npmjs.org/marked/-/marked-10.0.0.tgz",
7278
+      "integrity": "sha512-YiGcYcWj50YrwBgNzFoYhQ1hT6GmQbFG8SksnYJX1z4BXTHSOrz1GB5/Jm2yQvMg4nN1FHP4M6r03R10KrVUiA==",
7279
+      "bin": {
7280
+        "marked": "bin/marked.js"
7281
+      },
7282
+      "engines": {
7283
+        "node": ">= 18"
7284
+      }
7285
+    },
7264 7286
     "node_modules/merge-stream": {
7265 7287
       "version": "2.0.0",
7266 7288
       "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
@@ -8767,6 +8789,24 @@
8767 8789
         "url": "https://github.com/sponsors/ljharb"
8768 8790
       }
8769 8791
     },
8792
+    "node_modules/swiper": {
8793
+      "version": "11.0.3",
8794
+      "resolved": "https://registry.npmjs.org/swiper/-/swiper-11.0.3.tgz",
8795
+      "integrity": "sha512-MyV9ooQsriAe2EibeamqewLjgCfSvl2xoyratl6S3ln5BXDL4BzlO6mxcbLMCzQL6Z60b/u0AS/nKrepL0+TAg==",
8796
+      "funding": [
8797
+        {
8798
+          "type": "patreon",
8799
+          "url": "https://www.patreon.com/swiperjs"
8800
+        },
8801
+        {
8802
+          "type": "open_collective",
8803
+          "url": "http://opencollective.com/swiper"
8804
+        }
8805
+      ],
8806
+      "engines": {
8807
+        "node": ">= 4.7.0"
8808
+      }
8809
+    },
8770 8810
     "node_modules/symbol-tree": {
8771 8811
       "version": "3.2.4",
8772 8812
       "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",

+ 5 - 2
package.json

@@ -20,14 +20,17 @@
20 20
     "@capacitor/keyboard": "5.0.6",
21 21
     "@capacitor/preferences": "^5.0.6",
22 22
     "@capacitor/push-notifications": "^5.1.0",
23
+    "@capacitor/share": "^5.0.6",
23 24
     "@capacitor/status-bar": "5.0.6",
24 25
     "@codetrix-studio/capacitor-google-auth": "^3.3.4",
25
-    "@ionic/vue": "^7.0.0",
26
-    "@ionic/vue-router": "^7.0.0",
26
+    "@ionic/vue": "^7.5.4",
27
+    "@ionic/vue-router": "^7.5.4",
27 28
     "@vimeo/player": "^2.20.1",
28 29
     "axios": "^1.6.0",
29 30
     "ionicons": "^7.0.0",
31
+    "marked": "^10.0.0",
30 32
     "moment": "^2.29.4",
33
+    "swiper": "^11.0.3",
31 34
     "vue": "^3.2.45",
32 35
     "vue-core-video-player": "^0.2.0",
33 36
     "vue-router": "^4.1.6",

+ 12 - 8
src/components/Course.vue

@@ -1,30 +1,34 @@
1 1
 <template>
2 2
   <ion-card :router-link="'/tabs/course_detail/'+courseObj.id">
3
-    <img alt="Silhouette of mountains" :src="courseObj.feature_img"  v-if="courseObj.feature_img" />
3
+    <img alt="Silhouette of mountains" :src="feature_img"  v-if="feature_img" />
4 4
     <ion-card-header>
5 5
       <ion-card-title>{{ courseObj.name }}</ion-card-title>
6
-      <ion-card-subtitle>{{ courseObj.description }}</ion-card-subtitle>
7 6
     </ion-card-header>
8 7
 
9
-    <ion-card-content class='ion-text-wrap'>
10
-      {{ courseObj.description }}
11
-    </ion-card-content>
12 8
   </ion-card>
13 9
 </template>
14 10
 
15 11
 <script setup lang="ts">
16 12
   import { IonPage, IonHeader, IonToolbar, IonTitle, IonContent, IonIcon, onIonViewWillEnter, IonCard, IonCardHeader, IonCardTitle, IonCardSubtitle, IonCardContent } from '@ionic/vue';
17 13
 
18
-import { getTrainers, listCourses } from '@/settings';
14
+import { getTrainers, listCourses, BASE_URL } from '@/composable/settings';
19 15
   import { ref, onMounted } from 'vue'
20 16
   const props = defineProps({
21 17
     courseObj: Object,
22 18
   })
23
-
19
+  const feature_img  = ref()
24 20
   onMounted(() => {
25 21
     console.log("--- onmounted ---")
26 22
     console.log("name === ", props.courseObj)
23
+    if( props.courseObj.feature_img && !props.courseObj.feature_img.startsWith('http') ) {
24
+      feature_img.value = BASE_URL + props.courseObj.feature_img
25
+    }else {
26
+      feature_img.value = props.courseObj.feature_img
27
+    }
27 28
   })
29
+  const truncate = (str) => {
30
+    return "T "+str
31
+  }
28 32
 </script>
29 33
 
30 34
 <style scoped>
@@ -32,6 +36,6 @@ import { getTrainers, listCourses } from '@/settings';
32 36
   background-color:#f00;
33 37
 }
34 38
 ion-card-title {
35
-  font-size:1.5em;
39
+  font-size:1em;
36 40
 }
37 41
 </style>

+ 1 - 1
src/components/CourseMat.vue

@@ -14,7 +14,7 @@
14 14
 <script setup lang="ts">
15 15
   import { IonPage, IonHeader, IonToolbar, IonTitle, IonContent, IonIcon, onIonViewWillEnter, IonCard, IonCardHeader, IonCardTitle, IonCardSubtitle, IonCardContent } from '@ionic/vue';
16 16
   import moment from 'moment'
17
-import { getTrainers, listCourses } from '@/settings';
17
+import { getTrainers, listCourses } from '@/composable/settings';
18 18
   import { ref, onMounted  } from 'vue'
19 19
 
20 20
   const props = defineProps({

+ 47 - 0
src/components/CourseSchedule.vue

@@ -0,0 +1,47 @@
1
+<template>
2
+  <ion-card :router-link="'/tabs/course_detail/'+courseObj.id">
3
+    <img alt="Silhouette of mountains" :src="feature_img"  v-if="feature_img" />
4
+    <ion-card-header>
5
+      <ion-card-title>{{ courseObj.name }}</ion-card-title>
6
+    </ion-card-header>
7
+
8
+     <ion-card-content>
9
+       {{ fromTime.substring(0, 5) }} - {{ toTime.substring(0,5) }}
10
+    </ion-card-content>
11
+
12
+  </ion-card>
13
+</template>
14
+
15
+<script setup lang="ts">
16
+  import { IonPage, IonHeader, IonToolbar, IonTitle, IonContent, IonIcon, onIonViewWillEnter, IonCard, IonCardHeader, IonCardTitle, IonCardSubtitle, IonCardContent } from '@ionic/vue';
17
+
18
+import { getTrainers, listCourses, BASE_URL } from '@/composable/settings';
19
+  import { ref, onMounted } from 'vue'
20
+  const props = defineProps({
21
+    courseObj: Object,
22
+    fromTime: String, 
23
+    toTime: String,
24
+  })
25
+  const feature_img  = ref()
26
+  onMounted(() => {
27
+    console.log("--- onmounted ---")
28
+    console.log("name === ", props.courseObj)
29
+    if( props.courseObj.feature_img && !props.courseObj.feature_img.startsWith('http') ) {
30
+      feature_img.value = BASE_URL + props.courseObj.feature_img
31
+    }else {
32
+      feature_img.value = props.courseObj.feature_img
33
+    }
34
+  })
35
+  const truncate = (str) => {
36
+    return "T "+str
37
+  }
38
+</script>
39
+
40
+<style scoped>
41
+#container {
42
+  background-color:#f00;
43
+}
44
+ion-card-title {
45
+  font-size:1em;
46
+}
47
+</style>

+ 40 - 0
src/components/Post.vue

@@ -0,0 +1,40 @@
1
+<template>
2
+  <ion-card :router-link="'/tabs/post_detail/'+obj.id">
3
+    <img alt="Silhouette of mountains" :src="feature_img"  v-if="feature_img" />
4
+    <ion-card-header>
5
+      <ion-card-title>{{ obj.name }}</ion-card-title>
6
+    </ion-card-header>
7
+
8
+  </ion-card>
9
+</template>
10
+
11
+<script setup lang="ts">
12
+  import { IonPage, IonHeader, IonToolbar, IonTitle, IonContent, IonIcon, onIonViewWillEnter, IonCard, IonCardHeader, IonCardTitle, IonCardSubtitle, IonCardContent } from '@ionic/vue';
13
+
14
+import { getTrainers, listCourses, BASE_URL } from '@/composable/settings';
15
+  import { ref, onMounted } from 'vue'
16
+  const props = defineProps({
17
+    obj: Object,
18
+  })
19
+  const feature_img  = ref()
20
+  onMounted(() => {
21
+    console.log("--- onmounted ---")
22
+    if( props.obj.feature_img && !props.obj.feature_img.startsWith('http') ) {
23
+      feature_img.value = BASE_URL + props.obj.feature_img
24
+    }else {
25
+      feature_img.value = props.obj.feature_img
26
+    }
27
+  })
28
+  const truncate = (str) => {
29
+    return "T "+str
30
+  }
31
+</script>
32
+
33
+<style scoped>
34
+#container {
35
+  background-color:#f00;
36
+}
37
+ion-card-title {
38
+  font-size:1em;
39
+}
40
+</style>

+ 42 - 0
src/components/SearchEngine.vue

@@ -0,0 +1,42 @@
1
+<template>
2
+  <ion-searchbar :debounce="1000" @ionInput="handleInput($event)"></ion-searchbar>
3
+  <ion-list>
4
+    <template v-for="result in results">
5
+      <SearchItem :obj="result" />
6
+    </template>
7
+  </ion-list>
8
+
9
+</template>
10
+<script setup>
11
+import { IonItem, IonList, IonSearchbar,IonLabel } from '@ionic/vue';
12
+import { ref } from 'vue';
13
+import { searchMat } from '@/composable/settings';
14
+import SearchItem from '@/components/SearchItem.vue';
15
+
16
+ const data = [
17
+        'Amsterdam',
18
+        'Buenos Aires',
19
+        'Cairo',
20
+        'Geneva',
21
+        'Hong Kong',
22
+        'Istanbul',
23
+        'London',
24
+        'Madrid',
25
+        'New York',
26
+        'Panama City',
27
+];
28
+const results = ref();
29
+const handleInput = async (event) => {
30
+  console.log("handleInput")
31
+  console.log(event)
32
+  const query = event.target.value.toLowerCase();
33
+  console.log("query = ", query)
34
+  if(query == "") {
35
+    results.value = []
36
+  }else {
37
+    //results.value = data.filter((d) => d.toLowerCase().indexOf(query) > -1);
38
+    const r = await searchMat(query)
39
+    results.value = r.results
40
+  }
41
+}
42
+</script>

+ 36 - 0
src/components/SearchItem.vue

@@ -0,0 +1,36 @@
1
+<template>
2
+  <ion-item :router-link="'/tabs/mat_detail/'+obj.id">
3
+    <ion-thumbnail slot="start">
4
+      <img alt="Silhouette of mountains" :src="thmb"  v-if="thmb" />
5
+    </ion-thumbnail>
6
+    <ion-label>{{ name }} {{ dt }}</ion-label>
7
+  </ion-item>
8
+</template>
9
+
10
+<script setup>
11
+  import { IonItem, IonLabel, IonThumbnail } from '@ionic/vue';
12
+  import { ref, onMounted } from 'vue';
13
+  import moment from 'moment';
14
+  
15
+  const dt = ref()
16
+  const thmb = ref()
17
+  const name = ref()
18
+  
19
+  const props = defineProps({
20
+    obj: Object
21
+  })
22
+
23
+  onMounted(() => {
24
+    const obj = props.obj
25
+    let temp  = obj.display_datetime || obj.vimeo_created || obj.created_at 
26
+    dt.value = moment(temp).format('LLL'); 
27
+
28
+    if( obj.gifs && obj.gifs.length > 0 ) {
29
+      thmb.value = obj.gifs[0].sizes[2].link
30
+    }else {
31
+      thmb.value = obj.course_image
32
+    }
33
+    name.value = obj.vimeo_name || props.courseName
34
+  })
35
+
36
+</script>

+ 71 - 8
src/settings.ts

@@ -51,17 +51,28 @@ export const getTrainers = async () => {
51 51
   console.log(data)
52 52
   return data.results
53 53
 }
54
-export const listMats = async  () => {
54
+export const listMats = async  (ids=[]) => {
55 55
 
56 56
   const token = await Preferences.get({ key: 'token' });
57 57
   console.log("token = ", token)
58
-  const { data } = await axios.get(BASE_URL + "backend/api/mats/?ordering=-id", {
59
-    headers: { 
60
-    'Authorization': `Token ${token.value}`
61
-    }
62
-  })
63
-  console.log(data)
64
-  return data.results
58
+  console.log("ids ", ids)
59
+  if( ids.length == 0 ) {
60
+    const { data } = await axios.get(BASE_URL + "backend/api/mats/?ordering=-id", {
61
+      headers: { 
62
+      'Authorization': `Token ${token.value}`
63
+      }
64
+    })
65
+    
66
+    return data.results
67
+  }else {
68
+    const { data } = await axios.get(BASE_URL + "backend/api/mats/?ids="+ids.join(), {
69
+      headers: { 
70
+      'Authorization': `Token ${token.value}`
71
+      }
72
+    })
73
+    console.log("results = ", data.results)
74
+    return data.results
75
+  }
65 76
 }
66 77
 export const listCourses = async  () => {
67 78
 
@@ -126,3 +137,55 @@ export const listCourseMats = async  (cid) => {
126 137
   console.log(data)
127 138
   return data
128 139
 }
140
+export const getTodayProgs = async  () => {
141
+  const token = await Preferences.get({ key: 'token' });
142
+  console.log("token = ", token)
143
+  const { data } = await axios.get(BASE_URL + "backend/api/courses/today_progs/", {
144
+    headers: { 
145
+    'Authorization': `Token ${token.value}`
146
+    }
147
+  })
148
+  console.log(data)
149
+  return data
150
+}
151
+export const getLive = async  () => {
152
+  const token = await Preferences.get({ key: 'token' });
153
+  console.log("token = ", token)
154
+  const { data } = await axios.get(BASE_URL + "backend/api/scheds/get_live/", {
155
+    headers: { 
156
+    'Authorization': `Token ${token.value}`
157
+    }
158
+  })
159
+  console.log(data)
160
+  return data
161
+}
162
+export const getPosts = async (cat) => {
163
+  const token = await Preferences.get({ key: 'token' });
164
+  console.log("token = ", token)
165
+  const { data } = await axios.get(BASE_URL + `backend/api/posts/?category__slug=${cat}&status=active&ordering=-created_at`, {
166
+    headers: { 
167
+    'Authorization': `Token ${token.value}`
168
+    }
169
+  })
170
+  return data.results
171
+}
172
+export const getPost = async (pid) => {
173
+  const token = await Preferences.get({ key: 'token' });
174
+  console.log("token = ", token)
175
+  const { data } = await axios.get(BASE_URL + `backend/api/posts/${pid}/`, {
176
+    headers: { 
177
+    'Authorization': `Token ${token.value}`
178
+    }
179
+  })
180
+  return data
181
+}
182
+export const searchMat = async (s) => {
183
+  const token = await Preferences.get({ key: 'token' });
184
+  console.log("token = ", token)
185
+  const { data } = await axios.get(BASE_URL + `backend/api/mats/?search=${s}`, {
186
+    headers: { 
187
+    'Authorization': `Token ${token.value}`
188
+    }
189
+  })
190
+  return data
191
+}

+ 10 - 0
src/router/index.ts

@@ -45,6 +45,16 @@ const routes: Array<RouteRecordRaw> = [
45 45
         path: 'mat_detail/:id/',
46 46
         name: 'mat_detail', 
47 47
         component: () => import('@/views/CourseMatDetailPage.vue')
48
+      },
49
+      {
50
+        path: 'search_page/',
51
+        name: 'search_page', 
52
+        component: () => import('@/views/SearchPage.vue')
53
+      },
54
+      {
55
+        path: 'post_detail/:id/',
56
+        name: 'post_detail', 
57
+        component: () => import('@/views/PostDetailPage.vue')
48 58
       }
49 59
     ]
50 60
   }

+ 2 - 2
src/views/CourseDetailPage.vue

@@ -18,7 +18,7 @@
18 18
       <ion-grid>
19 19
         <ion-row>
20 20
           <ion-col size="12" size-md="4" size-lg="2" v-for="c in mats">
21
-            <CourseMat :obj="c" :course-name="course.name" />
21
+            <CourseMat :obj="c" :course-name="course.name" class='ion-no-margin' />
22 22
           </ion-col>
23 23
         </ion-row>
24 24
       </ion-grid> 
@@ -32,7 +32,7 @@ import { IonPage, IonHeader, IonToolbar, IonTitle, IonContent,   IonNavLink,
32 32
     IonButtons,
33 33
     IonBackButton, onIonViewWillEnter, IonRow, IonGrid, IonCol } from '@ionic/vue';
34 34
 import { useRoute } from 'vue-router';
35
-import { listCourseMats, getCourse } from '@/settings';
35
+import { listCourseMats, getCourse } from '@/composable/settings';
36 36
 import { ref } from 'vue';
37 37
 import CourseMat from '@/components/CourseMat.vue'
38 38
 

+ 46 - 4
src/views/CourseMatDetailPage.vue

@@ -13,8 +13,30 @@
13 13
       <div style="padding:56.25% 0 0 0;position:relative;" v-html="mat.embed" v-if='mat'></div>
14 14
       <div v-if="mat" class='ion-padding ion-text-wrap'>
15 15
         <h2 v-if='name'>{{ name }}</h2>
16
-        {{ mat }}
17
-      </div> 
16
+      </div>
17
+      <template v-if="mat">
18
+        <ion-item-divider>
19
+          <ion-label>Trainners</ion-label>
20
+        </ion-item-divider> 
21
+        <ion-list>
22
+          <ion-item v-for="m in mat.trainers">
23
+            <ion-avatar aria-hidden="true" slot="start">
24
+              <img  :src="BASE_URL+m.photo"  v-if="m.photo"/>
25
+            </ion-avatar>
26
+            <ion-label>{{ m.name }}</ion-label>
27
+          </ion-item>
28
+        </ion-list>
29
+        <ion-item-divider class='ion-margin-vertical'>
30
+          <ion-label>Related Trainings</ion-label>
31
+        </ion-item-divider> 
32
+      <swiper :slides-per-view="2" :loop="true" v-if="mats" class='ion-no-margin'>
33
+
34
+      <swiper-slide v-for="m in mats">
35
+        <CourseMat :obj="m" :course-name="mat.course_name" />
36
+      </swiper-slide>
37
+      </swiper>
38
+      </template>
39
+
18 40
     </ion-content>
19 41
   </ion-page>
20 42
 </template>
@@ -23,19 +45,39 @@
23 45
 import { IonPage, IonHeader, IonToolbar, IonTitle, IonContent,   IonNavLink,
24 46
     IonButton,
25 47
     IonButtons,
26
-    IonBackButton, onIonViewWillEnter, IonRow, IonGrid, IonCol } from '@ionic/vue';
48
+    IonBackButton, onIonViewWillEnter, IonRow, IonGrid, IonCol, IonItem, IonList, IonAvatar, IonLabel, IonItemDivider} from '@ionic/vue';
49
+
50
+import { Autoplay, Keyboard, Pagination, Scrollbar, Zoom } from 'swiper/modules';
51
+import { Swiper, SwiperSlide } from 'swiper/vue';
52
+
53
+
54
+import 'swiper/css';
55
+import 'swiper/css/autoplay';
56
+import 'swiper/css/keyboard';
57
+import 'swiper/css/pagination';
58
+import 'swiper/css/scrollbar';
59
+import 'swiper/css/zoom';
60
+import '@ionic/vue/css/ionic-swiper.css';
61
+
27 62
 import { useRoute } from 'vue-router';
28
-import { getMat } from '@/settings';
63
+import { getMat, BASE_URL, listMats } from '@/composable/settings';
29 64
 import { ref } from 'vue';
30 65
 import CourseMat from '@/components/CourseMat.vue'
31 66
 
32 67
 const route = useRoute();
33 68
 const { id } = route.params;
34 69
 
70
+const modules =  [Autoplay, Keyboard, Pagination, Scrollbar, Zoom]
71
+
35 72
 const mat = ref()
36 73
 const name = ref()
74
+const mats = ref()
75
+
76
+
37 77
 onIonViewWillEnter(async () => {
38 78
   mat.value = await getMat(id)
79
+  console.log(mat.value)
39 80
   name.value = mat.value.vimeo_name || mat.value.course_name
81
+  mats.value  = await listMats(mat.value.related_mats)
40 82
 })
41 83
 </script>

+ 77 - 0
src/views/PostDetailPage.vue

@@ -0,0 +1,77 @@
1
+
2
+<template>
3
+  <ion-page>
4
+    <ion-header>
5
+      <ion-toolbar>
6
+        <ion-buttons slot="start">
7
+          <ion-back-button></ion-back-button>
8
+          </ion-buttons>
9
+          <ion-title v-if="obj">{{ obj.name }}</ion-title>
10
+      </ion-toolbar>
11
+    </ion-header>
12
+    <ion-content :fullscreen="true">
13
+      <ion-header collapse="condense">
14
+        <ion-toolbar>
15
+          <ion-title size="large" v-if="obj">{{ obj.name }}</ion-title>
16
+        </ion-toolbar>
17
+      </ion-header>
18
+      <div v-html="render_body" class='ion-padding'></div>
19
+
20
+       <ion-fab slot="fixed" vertical="bottom" horizontal="end">
21
+      <ion-fab-button>
22
+        <ion-icon :icon="shareSocial" @click="socShare"></ion-icon>
23
+      </ion-fab-button>
24
+    </ion-fab>
25
+    </ion-content>
26
+  </ion-page>
27
+</template>
28
+
29
+<script setup lang="ts">
30
+
31
+import { marked } from 'marked';
32
+import { Share } from '@capacitor/share';
33
+
34
+import { IonPage, IonHeader, IonToolbar, IonTitle, IonContent,   IonNavLink,
35
+    IonButton,
36
+    IonButtons,
37
+    IonIcon,
38
+    IonBackButton, onIonViewWillEnter, IonRow, IonGrid, IonCol,
39
+    IonFab, IonFabButton } from '@ionic/vue';
40
+import {
41
+    chevronDownCircle,
42
+    chevronForwardCircle,
43
+    chevronUpCircle,
44
+    colorPalette,
45
+    shareSocial, 
46
+    document,
47
+    globe,
48
+  } from 'ionicons/icons';
49
+
50
+import { useRoute } from 'vue-router';
51
+import { listCourseMats, getCourse, getPost} from '@/composable/settings';
52
+import { ref } from 'vue';
53
+import CourseMat from '@/components/CourseMat.vue'
54
+
55
+const route = useRoute();
56
+const { id } = route.params;
57
+
58
+const obj = ref()
59
+const render_body = ref()
60
+
61
+onIonViewWillEnter(async () => {
62
+  const post  = await getPost(id)
63
+  console.log(" post ", post)
64
+  obj.value = post
65
+  render_body.value = marked(post.body)
66
+
67
+})
68
+const socShare = async () => {
69
+  console.log('share social')
70
+  await Share.share({
71
+    title: 'See cool stuff',
72
+    text: 'Really awesome thing you need to see right meow',
73
+    url: 'http://ionicframework.com/',
74
+    dialogTitle: 'Share with buddies',
75
+  });
76
+}
77
+</script>

+ 68 - 0
src/views/SearchPage.vue

@@ -0,0 +1,68 @@
1
+<template>
2
+  <ion-page>
3
+    <ion-header>
4
+      <ion-toolbar>
5
+        <ion-buttons slot="start">
6
+          <ion-back-button></ion-back-button>
7
+        </ion-buttons>
8
+        <ion-title>
9
+          Search
10
+        </ion-title>
11
+      </ion-toolbar>
12
+    </ion-header>
13
+    <ion-content class='ion-padding'>
14
+      <ion-searchbar :debounce="1000" @ionInput="handleInput($event)"></ion-searchbar>
15
+      <ion-list>
16
+        <template v-for="result in results">
17
+          <SearchItem :obj="result" />
18
+        </template>
19
+        <template v-if="processing">
20
+            <ion-progress-bar type="indeterminate"></ion-progress-bar>
21
+
22
+        </template>
23
+        <template v-if="is_empty">
24
+          <h1 class='ion-text-center'>Search not found</h1>
25
+        </template>
26
+      </ion-list>
27
+    </ion-content>
28
+  </ion-page>
29
+</template>
30
+
31
+<script setup lang="ts">
32
+import { IonPage, IonHeader, IonToolbar, IonTitle, IonContent, IonButton, onIonViewWillEnter, IonList, IonSearchbar, IonLabel,
33
+IonItem, IonBackButton, IonProgressBar } from '@ionic/vue';
34
+import ExploreContainer from '@/components/ExploreContainer.vue';
35
+import { defineComponent, onMounted } from 'vue';
36
+import { GoogleAuth } from '@codetrix-studio/capacitor-google-auth';
37
+import { searchMat } from '@/composable/settings';
38
+
39
+import { ref } from 'vue';
40
+import SearchItem from '@/components/SearchItem.vue';
41
+
42
+const results = ref();
43
+const is_empty = ref(false);
44
+const processing = ref(false)
45
+const handleInput = async (event) => {
46
+  console.log("handleInput")
47
+  console.log(event)
48
+  const query = event.target.value.toLowerCase();
49
+  console.log("query = ", query)
50
+  is_empty.value = false;
51
+  if(query == "") {
52
+    results.value = []
53
+  }else {
54
+    //results.value = data.filter((d) => d.toLowerCase().indexOf(query) > -1);
55
+    processing.value = true;
56
+    const r = await searchMat(query)
57
+    processing.value = false;
58
+
59
+    results.value = r.results
60
+    if(r.count == 0 ) {
61
+      is_empty.value = true;
62
+    }else {
63
+      is_empty.value = false;
64
+    }
65
+  }
66
+  //test
67
+}
68
+</script>

+ 104 - 66
src/views/Tab1Page.vue

@@ -2,84 +2,95 @@
2 2
   <ion-page>
3 3
     <ion-header>
4 4
       <ion-toolbar>
5
-        <ion-title>Tab 1</ion-title>
5
+        <ion-title>TMTLive</ion-title>
6
+       <ion-buttons slot="end">
7
+          <ion-button :router-link="'/tabs/search_page/'">
8
+            <ion-icon slot="icon-only" :icon="search"></ion-icon>
9
+          </ion-button>
10
+        </ion-buttons>
6 11
       </ion-toolbar>
7 12
     </ion-header>
8 13
     <ion-content>
9 14
       <ion-header collapse="condense">
10 15
         <ion-toolbar>
11
-          <ion-title size="large">Tab 1</ion-title>
16
+          <ion-title size="large">TMTLive</ion-title>
12 17
         </ion-toolbar>
13 18
       </ion-header>
19
+      <template v-if="liveObj && liveObj.result">
20
+        <div v-html="liveObj.result.embed_html"></div>
21
+      </template>
22
+      <div class='ion-padding'>
23
+      <h2>Today Programs</h2>
24
+      <swiper :slides-per-view="2" :loop="true" v-if="todays" class='ion-no-margin'>
25
+      <swiper-slide v-for="m in todays">
26
+        <CourseSchedule :course-obj="m.course_level.course" :from-time="m.from_time" :to-time='m.to_time'  />
27
+      </swiper-slide>
28
+      </swiper>
29
+      </div>
30
+      <template v-if="posts">
31
+        <div class='ion-padding'>
32
+          <h2>Muaythai Techniques</h2>
33
+          <swiper :slides-per-view="2"  :loop="true"  class='ion-no-margin'>
34
+          <swiper-slide v-for="m in posts">
35
+            <Post :obj="m" />
36
+          </swiper-slide>
37
+          </swiper>
38
+        </div>
39
+      </template>
40
+      <template v-if="mats">
41
+        <h2>Trainings</h2>
14 42
       <ion-grid>
43
+
15 44
         <ion-row>
16
-          <ion-col size="12" size-md="4" size-lg="2">
17
-            <ion-card>
18
-              <img alt="Silhouette of mountains" src="https://ionicframework.com/docs/img/demos/card-media.png" />
19
-              <ion-card-header>
20
-                <ion-card-title>Card Title</ion-card-title>
21
-                <ion-card-subtitle>Card Subtitle</ion-card-subtitle>
22
-              </ion-card-header>
23
-
24
-              <ion-card-content>
25
-                Here's a small text description for the card content. Nothing more, nothing less.
26
-              </ion-card-content>
27
-            </ion-card>
28
-          </ion-col>
29
-          <ion-col size="12" size-md="4" size-lg="2">
30
-            <ion-card>
31
-              <img alt="Silhouette of mountains" src="https://ionicframework.com/docs/img/demos/card-media.png" />
32
-              <ion-card-header>
33
-                <ion-card-title>Card Title</ion-card-title>
34
-                <ion-card-subtitle>Card Subtitle</ion-card-subtitle>
35
-              </ion-card-header>
36
-
37
-              <ion-card-content>
38
-                Here's a small text description for the card content. Nothing more, nothing less.
39
-              </ion-card-content>
40
-            </ion-card>
45
+          <ion-col size="12" size-md="4" size-lg="2" v-for="c in mats">
46
+            <CourseMat :obj="c" :course-name="c.course_name" class='ion-no-margin' />
41 47
           </ion-col>
42 48
         </ion-row>
43 49
       </ion-grid>
44
-      {{ token }}
50
+      </template>
45 51
       <ion-button router-link="/tabs/detail/3/" router-direction="forward">Click Me</ion-button>
46
-      {{ safeUrl }}
47
-      
52
+
48 53
       <div style="padding:56.25% 0 0 0;position:relative;"><iframe src="https://player.vimeo.com/video/880005259?badge=0&amp;autopause=0&amp;quality_selector=1&amp;player_id=0&amp;app_id=58479" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" style="position:absolute;top:0;left:0;width:100%;height:100%;" title="Muay Thai Advance"></iframe></div> 
49
-<!--
50
-<vue-core-video-player src="https://player.vimeo.com/progressive_redirect/playback/880005259/rendition/720p/file.mp4?loc=external&signature=5ddad1430211b33f65824444a3137fc0ec6fe61183d39997036d2dd3b78654f9"></vue-core-video-player> -->
51
-
52
-      Video
53
-
54
-
55
-<div id="made-in-ny" ref='video'></div>
56
-
57
-      <ul>
58
-        <li v-for="(item, index) in items">
59
-          {{ item.msg }}
60
-        </li>
61
-      </ul>
62
-      <ion-item v-for="t in trainers" :router-link="'/detail/'+t.id">
63
-        <ion-thumbnail slot="start" v-if="t.photo">
64
-          <img alt="Silhouette of mountains" :src="t.photo" />
65
-        </ion-thumbnail>
66
-        <ion-label class='ion-text-wrap'>{{ t.name }}</ion-label>
67
-      </ion-item>
68
-      <ion-icon icon="heart"></ion-icon>
69
-      <ion-button @click="scrollToBottom">Scroll to Bottom</ion-button>
70
-      {{ mats }}
54
+      <!--
55
+        <vue-core-video-player src="https://player.vimeo.com/progressive_redirect/playback/880005259/rendition/720p/file.mp4?loc=external&signature=5ddad1430211b33f65824444a3137fc0ec6fe61183d39997036d2dd3b78654f9"></vue-core-video-player> -->
56
+
57
+        Video
58
+
59
+
60
+        <div id="made-in-ny" ref='video'></div>
61
+
62
+        <ul>
63
+          <li v-for="(item, index) in items">
64
+            {{ item.msg }}
65
+          </li>
66
+        </ul>
67
+        <ion-item v-for="t in trainers" :router-link="'/detail/'+t.id">
68
+          <ion-thumbnail slot="start" v-if="t.photo">
69
+            <img alt="Silhouette of mountains" :src="t.photo" />
70
+          </ion-thumbnail>
71
+          <ion-label class='ion-text-wrap'>{{ t.name }}</ion-label>
72
+        </ion-item>
73
+        <ion-icon icon="heart"></ion-icon>
74
+        <ion-button @click="scrollToBottom">Scroll to Bottom</ion-button>
71 75
     </ion-content>
72 76
   </ion-page>
73 77
 </template>
74 78
 
75 79
 <script setup lang="ts">
76 80
 
77
-import { IonPage, IonHeader, IonToolbar, IonTitle, IonContent, IonIcon, onIonViewWillEnter, IonButton, 
78
-  onIonViewWillLeave, IonItem, IonLabel, IonThumbnail, IonCol, IonGrid, IonRow, IonCard, IonCardContent, IonCardHeader, IonCardSubtitle, IonCardTitle  } from '@ionic/vue';
81
+  import { IonPage, IonHeader, IonToolbar, IonTitle, IonContent, IonIcon, onIonViewWillEnter, IonButton, 
82
+  onIonViewWillLeave, IonItem, IonLabel, IonThumbnail, IonCol, IonGrid, IonRow, IonCard, IonCardContent, IonCardHeader, IonCardSubtitle, IonCardTitle, onIonViewDidEnter,  IonButtons } from '@ionic/vue';
83
+
79 84
 import ExploreContainer  from '@/components/ExploreContainer.vue';
85
+import CourseSchedule from '@/components/CourseSchedule.vue'
86
+import Post from '@/components/Post.vue'
87
+import CourseMat from '@/components/CourseMat.vue'
88
+import SearchEngine from '@/components/SearchEngine.vue'
89
+
80 90
 import { CapacitorHttp } from '@capacitor/core';
81 91
 import { ref } from 'vue';
82
-import { TOKEN, getProducts, setToken, getObject, getToken, getTrainers, listMats, storeAPNToken } from '@/settings';
92
+import { TOKEN, getProducts, setToken, getObject, getToken, getTrainers, listMats, storeAPNToken,
93
+getTodayProgs, getLive, getPosts} from '@/composable/settings';
83 94
 //import VueCoreVideoPlayer from 'vue-core-video-player'
84 95
 import { vueVimeoPlayer } from 'vue-vimeo-player'
85 96
 
@@ -89,25 +100,39 @@ import { PushNotifications } from '@capacitor/push-notifications';
89 100
 
90 101
 import Player from '@vimeo/player';
91 102
 
103
+import { Autoplay, Keyboard, Pagination, Scrollbar, Zoom } from 'swiper/modules';
104
+import { Swiper, SwiperSlide } from 'swiper/vue';
105
+import 'swiper/css';
106
+import 'swiper/css/autoplay';
107
+import 'swiper/css/keyboard';
108
+import 'swiper/css/pagination';
109
+import 'swiper/css/scrollbar';
110
+import 'swiper/css/zoom';
111
+import '@ionic/vue/css/ionic-swiper.css';
112
+import { search } from 'ionicons/icons';
113
+
92 114
 const safeUrl = "https://vimeo.com/880005259?share=copy"
93 115
 const token = ref()
94 116
 const trainers = ref([])
95 117
 const mats = ref()
96
- const scrollToBottom = () => {
97
-    console.log("scroll")
98
-    //content.value.$el.scrollToBottom(300);
118
+const todays = ref()
119
+const posts = ref()
120
+const scrollToBottom = () => {
121
+  console.log("scroll")
122
+  //content.value.$el.scrollToBottom(300);
123
+};
124
+const items = ref([{msg: 'foo'}, {msg: 'bar2'}])
125
+
126
+const options = {
127
+      id: 59777392,
128
+      width: 640,
129
+      loop: true
99 130
   };
100
-  const items = ref([{msg: 'foo'}, {msg: 'bar2'}])
101
-
102
-  const options = {
103
-        id: 59777392,
104
-        width: 640,
105
-        loop: true
106
-    };
107 131
  //const video = ref(null)
108 132
 
109 133
   //const player = new Player(video, options);
110 134
 
135
+const modules =  [Autoplay, Keyboard, Pagination, Scrollbar, Zoom]
111 136
   onIonViewWillEnter(async () => {
112 137
     //console.log("video " ,video)
113 138
     //console.log("video id", video.value.id)
@@ -128,16 +153,29 @@ const mats = ref()
128 153
     token.value = await getToken()
129 154
     trainers.value = await getTrainers()
130 155
     mats.value = await listMats()
156
+    posts.value = await getPosts("muaythai-techniques")
131 157
     console.log(" trainers => ", trainers)
132 158
 
133 159
     GoogleAuth.initialize();
134 160
     registerNotifications()
135 161
     addListeners()
162
+
163
+    todays.value = await getTodayProgs()
136 164
   })
137 165
 
138 166
   onIonViewWillLeave(() => {
139 167
     console.log("view will leave")
140
-  
168
+
169
+  })
170
+
171
+const liveObj = ref()
172
+  onIonViewDidEnter(async() => {
173
+    setInterval( async () => {
174
+      console.log("did enter")
175
+      if( liveObj.value == null ||  liveObj.value.result == false ) {
176
+        liveObj.value = await getLive()
177
+      }
178
+    }, 1000)
141 179
   })
142 180
 
143 181
   const doGet = async () => {

+ 3 - 3
src/views/Tab2Page.vue

@@ -13,8 +13,8 @@
13 13
       </ion-header>
14 14
       <ion-grid>
15 15
         <ion-row>
16
-          <ion-col size="12" size-md="4" size-lg="2" v-for="c in courses">
17
-            <Course :course-obj="c" />
16
+          <ion-col size="6" size-md="4" size-lg="2" v-for="c in courses">
17
+            <Course :course-obj="c" class="ion-no-margin" />
18 18
           </ion-col>
19 19
         </ion-row>
20 20
       </ion-grid>
@@ -27,7 +27,7 @@ import { IonPage, IonHeader, IonToolbar, IonTitle, IonContent, IonGrid, IonRow,
27 27
 import Course from '@/components/Course.vue';
28 28
 import { ref } from 'vue';
29 29
 
30
-import {listCourses} from '@/settings';
30
+import {listCourses} from '@/composable/settings';
31 31
 const courses = ref([])
32 32
 
33 33
 onIonViewWillEnter(async () => {