Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 83fc10878b | |||
| 576f794ff7 | |||
| bd1db2a468 | |||
| 905214b49e | |||
| 74d3f395ce | |||
| 034e7e1e27 | |||
| 09bffa78a3 | |||
| f0088b08b6 | |||
| 91a43a37b4 | |||
| 4a2aa59c77 | |||
| 3aab0b826b | |||
| 6e03bc997c | |||
| f04bb1843c |
44
README.md
44
README.md
@@ -12,6 +12,7 @@
|
|||||||
- **Автоопределение дня недели** — будни/выходные.
|
- **Автоопределение дня недели** — будни/выходные.
|
||||||
- **Полное расписание** — по кнопке раскрывается вся таблица
|
- **Полное расписание** — по кнопке раскрывается вся таблица
|
||||||
(4 остановки: Кандалакша · ПЗ→Зашеек · Зашеек · ПЗ→Кандалакша).
|
(4 остановки: Кандалакша · ПЗ→Зашеек · Зашеек · ПЗ→Кандалакша).
|
||||||
|
- **Пуш-уведомления** — через RuStore Push, см. раздел ниже.
|
||||||
- Тёмная тема, живые часы МСК, подсветка ближайших рейсов (< 15 мин).
|
- Тёмная тема, живые часы МСК, подсветка ближайших рейсов (< 15 мин).
|
||||||
|
|
||||||
## Технические данные
|
## Технические данные
|
||||||
@@ -24,6 +25,42 @@
|
|||||||
| Сборка | Gradle 8.6 + AGP 8.2.2 |
|
| Сборка | Gradle 8.6 + AGP 8.2.2 |
|
||||||
| Подпись | keystore `~/bus127.keystore` (alias `bus127`) |
|
| Подпись | keystore `~/bus127.keystore` (alias `bus127`) |
|
||||||
|
|
||||||
|
## Пуш-уведомления (RuStore)
|
||||||
|
|
||||||
|
Приложение интегрировано с [RuStore Push SDK](https://www.rustore.ru/help/sdk/push-notifications)
|
||||||
|
(аналог Firebase Cloud Messaging). Проект в консоли RuStore привязан к пакету
|
||||||
|
`ru.ua1zbe.bus127` и подписи release-keystore; пока подпись не меняется, перерегистрация не нужна.
|
||||||
|
|
||||||
|
Для доставки на устройстве должен быть установлен и авторизован **RuStore**.
|
||||||
|
|
||||||
|
Рассылка выполняется с сервера через API RuStore:
|
||||||
|
|
||||||
|
```
|
||||||
|
POST https://vkpns.rustore.ru/v1/projects/{project_id}/messages:send
|
||||||
|
Authorization: Bearer {service-token}
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"message": {
|
||||||
|
"token": "{push-token устройства}",
|
||||||
|
"notification": {
|
||||||
|
"title": "Автобус 127",
|
||||||
|
"body": "Ближайший рейс через 5 мин"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `project_id` и `service-token` берутся в консоли RuStore: **Push-уведомления > Проекты**.
|
||||||
|
В приложение попадает только `project_id`; сервисный токен в APK не хранится.
|
||||||
|
- `push-token` устройства приходит в `onNewToken` (лог `RuStorePush`) и сохраняется
|
||||||
|
в `SharedPreferences` `bus127_push`.
|
||||||
|
- Приложение подписывается на топик `bus127` — топик должен существовать в консоли.
|
||||||
|
Сообщения без поля `topic` приходят всем устройствам проекта.
|
||||||
|
- Если в запросе есть секция `notification` — уведомление показывает сам SDK;
|
||||||
|
если переданы только `data` (`title`/`body`, необязательный `url`) — приложение
|
||||||
|
показывает его само и открывает ссылку по клику.
|
||||||
|
|
||||||
## Структура
|
## Структура
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -32,7 +69,12 @@ apk-webview/ # исходники APK (WebView)
|
|||||||
│ ├── build.gradle
|
│ ├── build.gradle
|
||||||
│ └── src/main/
|
│ └── src/main/
|
||||||
│ ├── AndroidManifest.xml
|
│ ├── AndroidManifest.xml
|
||||||
│ ├── java/ru/ua1zbe/bus127/MainActivity.java
|
│ ├── java/ru/ua1zbe/bus127/
|
||||||
|
│ │ ├── MainActivity.java # WebView + запрос разрешения на уведомления
|
||||||
|
│ │ ├── Bus127App.java # инициализация RuStore Push SDK
|
||||||
|
│ │ ├── PushMessagingService.java # приём уведомлений
|
||||||
|
│ │ ├── PushIntents.java # переход по клику
|
||||||
|
│ │ └── push/PushStore.java # хранение push-токена
|
||||||
│ ├── assets/index.html # интерфейс (HTML/JS)
|
│ ├── assets/index.html # интерфейс (HTML/JS)
|
||||||
│ └── res/values/strings.xml
|
│ └── res/values/strings.xml
|
||||||
└── settings.gradle, build.gradle, gradle.properties
|
└── settings.gradle, build.gradle, gradle.properties
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ android {
|
|||||||
applicationId "ru.ua1zbe.bus127"
|
applicationId "ru.ua1zbe.bus127"
|
||||||
minSdk 26
|
minSdk 26
|
||||||
targetSdk 34
|
targetSdk 34
|
||||||
versionCode 8
|
versionCode 11
|
||||||
versionName "2.6"
|
versionName "2.6.2"
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@@ -35,3 +35,7 @@ android {
|
|||||||
targetCompatibility JavaVersion.VERSION_17
|
targetCompatibility JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation "ru.rustore.sdk:pushclient:7.4.0"
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +1,34 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name=".Bus127App"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:usesCleartextTraffic="true"
|
android:usesCleartextTraffic="true"
|
||||||
android:theme="@android:style/Theme.Material.NoActionBar">
|
android:theme="@android:style/Theme.Material.NoActionBar">
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".PushMessagingService"
|
||||||
|
android:exported="true"
|
||||||
|
tools:ignore="ExportedService">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="ru.rustore.sdk.pushclient.MESSAGING_EVENT" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="ru.rustore.sdk.pushclient.default_notification_icon"
|
||||||
|
android:resource="@mipmap/ic_launcher" />
|
||||||
|
<meta-data
|
||||||
|
android:name="ru.rustore.sdk.pushclient.default_notification_channel_id"
|
||||||
|
android:value="@string/push_channel_id" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
|||||||
@@ -255,13 +255,17 @@ let currentStop = "Полярные Зори";
|
|||||||
// Date#getDay(): 0 — воскресенье, 6 — суббота
|
// Date#getDay(): 0 — воскресенье, 6 — суббота
|
||||||
function dayKeyFor(dow){ return dow===0 ? "sunday" : dow===6 ? "saturday" : "weekday"; }
|
function dayKeyFor(dow){ return dow===0 ? "sunday" : dow===6 ? "saturday" : "weekday"; }
|
||||||
|
|
||||||
|
// Карточки ближайших рейсов всегда показывают реальный сегодняшний день
|
||||||
|
function todayKey(){ return dayKeyFor(nowMs().day); }
|
||||||
|
|
||||||
|
// Выбор пользователя — только для полного расписания
|
||||||
function sched(){ return SCHEDULES[activeDay]; }
|
function sched(){ return SCHEDULES[activeDay]; }
|
||||||
|
|
||||||
function render(){
|
function render(){
|
||||||
const now = nowMs();
|
const now = nowMs();
|
||||||
const curMin = now.hour*60 + now.minute;
|
const curMin = now.hour*60 + now.minute;
|
||||||
const dirs = DIRECTIONS[currentStop];
|
const dirs = DIRECTIONS[currentStop];
|
||||||
const data = sched();
|
const data = SCHEDULES[todayKey()];
|
||||||
document.getElementById("dirBox").innerHTML = dirs.map(d=>{
|
document.getElementById("dirBox").innerHTML = dirs.map(d=>{
|
||||||
const upcoming = data
|
const upcoming = data
|
||||||
.map((r,ri)=>{
|
.map((r,ri)=>{
|
||||||
@@ -303,12 +307,12 @@ function renderDay(){
|
|||||||
document.getElementById("dayBadge").innerHTML = `Сегодня <b>${DAYS[dow]}</b>`;
|
document.getElementById("dayBadge").innerHTML = `Сегодня <b>${DAYS[dow]}</b>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Подсветка выбранного типа дня и перерисовка расписания
|
// Подсветка выбранного типа дня. Таблица полного расписания перерисовывается,
|
||||||
|
// карточки ближайших рейсов не трогаем — они всегда на сегодня.
|
||||||
function selectDay(key){
|
function selectDay(key){
|
||||||
activeDay = key;
|
activeDay = key;
|
||||||
document.querySelectorAll("#dayPick button").forEach(b=>
|
document.querySelectorAll("#dayPick button").forEach(b=>
|
||||||
b.classList.toggle("active", b.dataset.day === key));
|
b.classList.toggle("active", b.dataset.day === key));
|
||||||
render();
|
|
||||||
renderTable();
|
renderTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,9 +346,9 @@ document.getElementById("fullBtn").addEventListener("click",()=>{
|
|||||||
b.classList.toggle("open", !open);
|
b.classList.toggle("open", !open);
|
||||||
});
|
});
|
||||||
|
|
||||||
let activeDay = dayKeyFor(nowMs().day); // по умолчанию — реальный день недели
|
let activeDay = todayKey(); // по умолчанию — реальный день недели
|
||||||
selectDay(activeDay);
|
selectDay(activeDay);
|
||||||
tickClock(); renderDay();
|
tickClock(); renderDay(); render();
|
||||||
setInterval(tickClock,1000);
|
setInterval(tickClock,1000);
|
||||||
setInterval(render,30000);
|
setInterval(render,30000);
|
||||||
setInterval(renderDay,60000);
|
setInterval(renderDay,60000);
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package ru.ua1zbe.bus127;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.app.NotificationChannel;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import ru.rustore.sdk.pushclient.RuStorePushClient;
|
||||||
|
import ru.ua1zbe.bus127.push.PushStore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Инициализация RuStore Push SDK.
|
||||||
|
*
|
||||||
|
* project_id и topic берутся из консоли RuStore (Push-уведомления > Проекты).
|
||||||
|
* Сервисный токен в приложении не хранится — он нужен только серверу
|
||||||
|
* для вызова https://vkpns.rustore.ru/v1/projects/{project_id}/messages:send
|
||||||
|
*/
|
||||||
|
public class Bus127App extends Application {
|
||||||
|
|
||||||
|
public static final String CHANNEL_ID = "bus127_push";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
createPushChannel();
|
||||||
|
initRuStorePush();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createPushChannel() {
|
||||||
|
NotificationManager nm = getSystemService(NotificationManager.class);
|
||||||
|
nm.createNotificationChannel(new NotificationChannel(
|
||||||
|
CHANNEL_ID,
|
||||||
|
getString(R.string.push_channel_name),
|
||||||
|
NotificationManager.IMPORTANCE_HIGH));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initRuStorePush() {
|
||||||
|
try {
|
||||||
|
RuStorePushClient.INSTANCE.init(this, RUSTORE_PROJECT_ID);
|
||||||
|
RuStorePushClient.INSTANCE.getToken()
|
||||||
|
.addOnSuccessListener(token -> {
|
||||||
|
Log.i(TAG, "push token: " + token);
|
||||||
|
PushStore.setToken(this, token);
|
||||||
|
RuStorePushClient.INSTANCE.subscribeToTopic(PUSH_TOPIC)
|
||||||
|
.addOnSuccessListener(v ->
|
||||||
|
Log.i(TAG, "subscribed to " + PUSH_TOPIC))
|
||||||
|
.addOnFailureListener(e ->
|
||||||
|
Log.w(TAG, "subscribe to " + PUSH_TOPIC + " failed", e));
|
||||||
|
})
|
||||||
|
.addOnFailureListener(e ->
|
||||||
|
Log.w(TAG, "getToken failed", e));
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.w(TAG, "init failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String TAG = "RuStorePush";
|
||||||
|
private static final String RUSTORE_PROJECT_ID = "upSEOtZyXP96tskl4hf-442AqciCUTxs";
|
||||||
|
private static final String PUSH_TOPIC = "bus127";
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
package ru.ua1zbe.bus127;
|
package ru.ua1zbe.bus127;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.webkit.WebSettings;
|
import android.webkit.WebSettings;
|
||||||
@@ -13,6 +16,7 @@ public class MainActivity extends Activity {
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
// экран не гаснет, пока открыто расписание
|
// экран не гаснет, пока открыто расписание
|
||||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||||
|
requestNotificationsPermission();
|
||||||
WebView webView = new WebView(this);
|
WebView webView = new WebView(this);
|
||||||
WebSettings settings = webView.getSettings();
|
WebSettings settings = webView.getSettings();
|
||||||
settings.setJavaScriptEnabled(true);
|
settings.setJavaScriptEnabled(true);
|
||||||
@@ -22,4 +26,13 @@ public class MainActivity extends Activity {
|
|||||||
webView.loadUrl("file:///android_asset/index.html");
|
webView.loadUrl("file:///android_asset/index.html");
|
||||||
setContentView(webView);
|
setContentView(webView);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Android 13+ требует явного согласия на показ уведомлений
|
||||||
|
private void requestNotificationsPermission() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
|
||||||
|
&& checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS)
|
||||||
|
!= PackageManager.PERMISSION_GRANTED) {
|
||||||
|
requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package ru.ua1zbe.bus127;
|
||||||
|
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
/** Открытие расписания по клику на уведомление. */
|
||||||
|
final class PushIntents {
|
||||||
|
|
||||||
|
private PushIntents() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static PendingIntent web(Context context, String url) {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
||||||
|
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
return PendingIntent.getActivity(context, 0, intent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package ru.ua1zbe.bus127;
|
||||||
|
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import ru.rustore.sdk.pushclient.messaging.exception.RuStorePushClientException;
|
||||||
|
import ru.rustore.sdk.pushclient.messaging.model.RemoteMessage;
|
||||||
|
import ru.rustore.sdk.pushclient.messaging.service.RuStoreMessagingService;
|
||||||
|
import ru.ua1zbe.bus127.push.PushStore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Приём пуш-уведомлений RuStore.
|
||||||
|
*
|
||||||
|
* Если сервер прислал секцию notification — SDK показывает уведомление сам.
|
||||||
|
* Если пришли только data (title/body) — показываем уведомление сами.
|
||||||
|
*/
|
||||||
|
public class PushMessagingService extends RuStoreMessagingService {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNewToken(String token) {
|
||||||
|
Log.i(TAG, "onNewToken: " + token);
|
||||||
|
PushStore.setToken(this, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessageReceived(RemoteMessage message) {
|
||||||
|
if (message.getNotification() != null) {
|
||||||
|
Log.i(TAG, "push displayed by SDK: " + message.getData());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Map<String, String> data = message.getData();
|
||||||
|
String title = data.get("title");
|
||||||
|
if (title == null || title.isEmpty()) {
|
||||||
|
title = getString(R.string.app_name);
|
||||||
|
}
|
||||||
|
String body = data.get("body");
|
||||||
|
if (body == null) {
|
||||||
|
body = "";
|
||||||
|
}
|
||||||
|
if (!title.isEmpty() || !body.isEmpty()) {
|
||||||
|
showPush(title, body, data.get("url"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDeletedMessages() {
|
||||||
|
Log.i(TAG, "onDeletedMessages");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(List<? extends RuStorePushClientException> errors) {
|
||||||
|
for (RuStorePushClientException e : errors) {
|
||||||
|
Log.w(TAG, "error", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showPush(String title, String body, String url) {
|
||||||
|
Notification.Builder b = new Notification.Builder(this, Bus127App.CHANNEL_ID)
|
||||||
|
.setContentTitle(title)
|
||||||
|
.setContentText(body)
|
||||||
|
.setSmallIcon(R.mipmap.ic_launcher)
|
||||||
|
.setAutoCancel(true);
|
||||||
|
if (url != null && !url.isEmpty()) {
|
||||||
|
b.setContentIntent(PushIntents.web(this, url));
|
||||||
|
}
|
||||||
|
getSystemService(NotificationManager.class).notify(NOTIFICATION_ID, b.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String TAG = "RuStorePush";
|
||||||
|
private static final int NOTIFICATION_ID = 127;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package ru.ua1zbe.bus127.push;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
|
/** Хранение push-токена устройства, чтобы отправлять ему уведомления с сервера. */
|
||||||
|
public final class PushStore {
|
||||||
|
|
||||||
|
private static final String PREFS = "bus127_push";
|
||||||
|
private static final String KEY_TOKEN = "push_token";
|
||||||
|
|
||||||
|
private PushStore() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SharedPreferences sp(Context context) {
|
||||||
|
return context.getApplicationContext()
|
||||||
|
.getSharedPreferences(PREFS, Context.MODE_PRIVATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getToken(Context context) {
|
||||||
|
return sp(context).getString(KEY_TOKEN, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setToken(Context context, String token) {
|
||||||
|
sp(context).edit().putString(KEY_TOKEN, token).apply();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Автобус 127</string>
|
<string name="app_name">Автобус 127</string>
|
||||||
|
<string name="push_channel_id">bus127_push</string>
|
||||||
|
<string name="push_channel_name">Пуш-уведомления</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -2,7 +2,11 @@ pluginManagement {
|
|||||||
repositories { google(); mavenCentral(); gradlePluginPortal() }
|
repositories { google(); mavenCentral(); gradlePluginPortal() }
|
||||||
}
|
}
|
||||||
dependencyResolutionManagement {
|
dependencyResolutionManagement {
|
||||||
repositories { google(); mavenCentral() }
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
maven { url "https://nexus-external.rustore.ru/repository/maven-rustore-exposed/" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
rootProject.name = "BusSchedule127"
|
rootProject.name = "BusSchedule127"
|
||||||
include ':app'
|
include ':app'
|
||||||
|
|||||||
@@ -255,13 +255,17 @@ let currentStop = "Полярные Зори";
|
|||||||
// Date#getDay(): 0 — воскресенье, 6 — суббота
|
// Date#getDay(): 0 — воскресенье, 6 — суббота
|
||||||
function dayKeyFor(dow){ return dow===0 ? "sunday" : dow===6 ? "saturday" : "weekday"; }
|
function dayKeyFor(dow){ return dow===0 ? "sunday" : dow===6 ? "saturday" : "weekday"; }
|
||||||
|
|
||||||
|
// Карточки ближайших рейсов всегда показывают реальный сегодняшний день
|
||||||
|
function todayKey(){ return dayKeyFor(nowMs().day); }
|
||||||
|
|
||||||
|
// Выбор пользователя — только для полного расписания
|
||||||
function sched(){ return SCHEDULES[activeDay]; }
|
function sched(){ return SCHEDULES[activeDay]; }
|
||||||
|
|
||||||
function render(){
|
function render(){
|
||||||
const now = nowMs();
|
const now = nowMs();
|
||||||
const curMin = now.hour*60 + now.minute;
|
const curMin = now.hour*60 + now.minute;
|
||||||
const dirs = DIRECTIONS[currentStop];
|
const dirs = DIRECTIONS[currentStop];
|
||||||
const data = sched();
|
const data = SCHEDULES[todayKey()];
|
||||||
document.getElementById("dirBox").innerHTML = dirs.map(d=>{
|
document.getElementById("dirBox").innerHTML = dirs.map(d=>{
|
||||||
const upcoming = data
|
const upcoming = data
|
||||||
.map((r,ri)=>{
|
.map((r,ri)=>{
|
||||||
@@ -303,12 +307,12 @@ function renderDay(){
|
|||||||
document.getElementById("dayBadge").innerHTML = `Сегодня <b>${DAYS[dow]}</b>`;
|
document.getElementById("dayBadge").innerHTML = `Сегодня <b>${DAYS[dow]}</b>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Подсветка выбранного типа дня и перерисовка расписания
|
// Подсветка выбранного типа дня. Таблица полного расписания перерисовывается,
|
||||||
|
// карточки ближайших рейсов не трогаем — они всегда на сегодня.
|
||||||
function selectDay(key){
|
function selectDay(key){
|
||||||
activeDay = key;
|
activeDay = key;
|
||||||
document.querySelectorAll("#dayPick button").forEach(b=>
|
document.querySelectorAll("#dayPick button").forEach(b=>
|
||||||
b.classList.toggle("active", b.dataset.day === key));
|
b.classList.toggle("active", b.dataset.day === key));
|
||||||
render();
|
|
||||||
renderTable();
|
renderTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,9 +346,9 @@ document.getElementById("fullBtn").addEventListener("click",()=>{
|
|||||||
b.classList.toggle("open", !open);
|
b.classList.toggle("open", !open);
|
||||||
});
|
});
|
||||||
|
|
||||||
let activeDay = dayKeyFor(nowMs().day); // по умолчанию — реальный день недели
|
let activeDay = todayKey(); // по умолчанию — реальный день недели
|
||||||
selectDay(activeDay);
|
selectDay(activeDay);
|
||||||
tickClock(); renderDay();
|
tickClock(); renderDay(); render();
|
||||||
setInterval(tickClock,1000);
|
setInterval(tickClock,1000);
|
||||||
setInterval(render,30000);
|
setInterval(render,30000);
|
||||||
setInterval(renderDay,60000);
|
setInterval(renderDay,60000);
|
||||||
|
|||||||
Reference in New Issue
Block a user