diff --git a/.cnb.yml b/.cnb.yml index 6d5f8a2..033ee61 100644 --- a/.cnb.yml +++ b/.cnb.yml @@ -23,6 +23,7 @@ main: yarn install --frozen-lockfile yarn run lint +"**": web_trigger_job: - runner: cpus: 16 diff --git a/.gitignore b/.gitignore index ce3579c..fe2a649 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,7 @@ app-example # generated native folders /ios /android +**/android/build/ # OTA !assets/certificate.pem diff --git a/app.config.ts b/app.config.ts index f7184c7..ea786e4 100644 --- a/app.config.ts +++ b/app.config.ts @@ -45,6 +45,9 @@ const config: ExpoConfig = { NSAllowsArbitraryLoadsInWebContent: true, }, }, + entitlements: { + "com.apple.security.application-groups": ["group.dev.tokenteam.iwut"], + }, }, android: { package: IS_DEV ? "dev.tokenteam.iwut.dev" : "dev.tokenteam.iwut", @@ -84,6 +87,7 @@ const config: ExpoConfig = { }, ], "@sentry/react-native", + "@bacons/apple-targets", "./plugins/with-gradle-props.js", ], experiments: { diff --git a/app/_layout.tsx b/app/_layout.tsx index 53293ad..10c1b6a 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -36,6 +36,8 @@ import Toast from "react-native-toast-message"; import { Themes } from "@/constants/theme"; import { useColorScheme } from "@/hooks/use-color-scheme"; +import { syncWidgetData } from "@/services/widget-sync"; +import { useCourseStore } from "@/store/course"; import { useThemeStore } from "@/store/theme"; import { useUpdateStore } from "@/store/update"; @@ -74,6 +76,19 @@ function RootLayout() { useUpdateStore.getState().check(); }, []); + useEffect(() => { + syncWidgetData().catch(() => {}); + const unsub = useCourseStore.subscribe((state, prev) => { + if ( + state.courses !== prev.courses || + state.termStart !== prev.termStart + ) { + syncWidgetData().catch(() => {}); + } + }); + return unsub; + }, []); + const onLayoutRootView = useCallback(() => { if (fontsLoaded || fontError) { void SplashScreen.hideAsync(); diff --git a/modules/network-reporter/android/src/main/java/dev/tokenteam/iwut/networkreporter/NetworkReporterModule.kt b/modules/network-reporter/android/src/main/java/dev/tokenteam/iwut/networkreporter/NetworkReporterModule.kt index a32e098..a66750f 100644 --- a/modules/network-reporter/android/src/main/java/dev/tokenteam/iwut/networkreporter/NetworkReporterModule.kt +++ b/modules/network-reporter/android/src/main/java/dev/tokenteam/iwut/networkreporter/NetworkReporterModule.kt @@ -8,24 +8,24 @@ import expo.modules.kotlin.modules.Module import expo.modules.kotlin.modules.ModuleDefinition class NetworkReporterModule : Module() { - override fun definition() = ModuleDefinition { - Name("NetworkReporter") + override fun definition() = ModuleDefinition { + Name("NetworkReporter") - AsyncFunction("reportWifiConnectivity") { hasConnectivity: Boolean -> - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - return@AsyncFunction false - } + AsyncFunction("reportWifiConnectivity") { hasConnectivity: Boolean -> + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + return@AsyncFunction false + } - val context = appContext.reactContext ?: return@AsyncFunction false - val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) - as? ConnectivityManager ?: return@AsyncFunction false + val context = appContext.reactContext ?: return@AsyncFunction false + val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) + as? ConnectivityManager ?: return@AsyncFunction false - @Suppress("DEPRECATION") - val wifi = cm.allNetworks.firstOrNull { - cm.getNetworkCapabilities(it)?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true - } ?: return@AsyncFunction false + @Suppress("DEPRECATION") + val wifi = cm.allNetworks.firstOrNull { + cm.getNetworkCapabilities(it)?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true + } ?: return@AsyncFunction false - runCatching { cm.reportNetworkConnectivity(wifi, hasConnectivity) }.isSuccess + runCatching { cm.reportNetworkConnectivity(wifi, hasConnectivity) }.isSuccess + } } - } } diff --git a/modules/widget/android/build.gradle b/modules/widget/android/build.gradle new file mode 100644 index 0000000..e3e8062 --- /dev/null +++ b/modules/widget/android/build.gradle @@ -0,0 +1,18 @@ +plugins { + id 'com.android.library' + id 'expo-module-gradle-plugin' +} + +group = 'dev.tokenteam.iwut' + +expoModule { + canBePublished = false +} + +android { + namespace "dev.tokenteam.iwut.widget" +} + +dependencies { + implementation "com.google.code.gson:gson:2.11.0" +} diff --git a/modules/widget/android/src/main/AndroidManifest.xml b/modules/widget/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..6da85b5 --- /dev/null +++ b/modules/widget/android/src/main/AndroidManifest.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/modules/widget/android/src/main/java/dev/tokenteam/iwut/widget/ScheduleData.kt b/modules/widget/android/src/main/java/dev/tokenteam/iwut/widget/ScheduleData.kt new file mode 100644 index 0000000..a5d55b8 --- /dev/null +++ b/modules/widget/android/src/main/java/dev/tokenteam/iwut/widget/ScheduleData.kt @@ -0,0 +1,88 @@ +package dev.tokenteam.iwut.widget + +import android.content.Context +import com.google.gson.Gson +import com.google.gson.annotations.SerializedName +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Locale +import java.util.concurrent.TimeUnit + +data class WidgetCourse( + @SerializedName("name") val name: String = "", + @SerializedName("room") val room: String = "", + @SerializedName("day") val day: Int = 1, + @SerializedName("weekStart") val weekStart: Int = 1, + @SerializedName("weekEnd") val weekEnd: Int = 20, + @SerializedName("sectionStart") val sectionStart: Int = 0, + @SerializedName("sectionEnd") val sectionEnd: Int = 0, + @SerializedName("startTime") val startTime: String = "", + @SerializedName("endTime") val endTime: String = "", +) + +data class ScheduleWidgetData( + @SerializedName("courses") val courses: List = emptyList(), + @SerializedName("termStart") val termStart: String = "", + @SerializedName("updatedAt") val updatedAt: String = "", +) + +object ScheduleData { + private val gson = Gson() + private val DAY_NAMES = arrayOf("", "周一", "周二", "周三", "周四", "周五", "周六", "周日") + + fun load(context: Context): ScheduleWidgetData? { + val prefs = context.getSharedPreferences("widget_data", Context.MODE_PRIVATE) + val json = prefs.getString("schedule", null) ?: return null + return try { + gson.fromJson(json, ScheduleWidgetData::class.java) + } catch (e: Exception) { + null + } + } + + fun getCurrentWeek(termStart: String): Int { + val sdf = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) + val startDate = try { + sdf.parse(termStart) ?: return 1 + } catch (e: Exception) { + return 1 + } + val now = Calendar.getInstance().time + val diffMs = now.time - startDate.time + if (diffMs < 0) return 0 + val diffDays = TimeUnit.MILLISECONDS.toDays(diffMs) + return (diffDays / 7 + 1).toInt() + } + + fun getDayOfWeek(): Int { + val cal = Calendar.getInstance() + val dow = cal.get(Calendar.DAY_OF_WEEK) + return if (dow == Calendar.SUNDAY) 7 else dow - 1 + } + + fun getTomorrowDayOfWeek(): Int { + val today = getDayOfWeek() + return if (today == 7) 1 else today + 1 + } + + fun getTomorrowWeek(termStart: String): Int { + val today = getDayOfWeek() + val week = getCurrentWeek(termStart) + return if (today == 7) week + 1 else week + } + + fun getWeekStr(week: Int): String = "第${week}周" + + fun getDateStr(): String { + val cal = Calendar.getInstance() + return "${cal.get(Calendar.MONTH) + 1}月${cal.get(Calendar.DAY_OF_MONTH)}日" + } + + fun getDayOfWeekStr(day: Int): String = DAY_NAMES.getOrElse(day) { "" } + + fun parseTimeToMinutes(time: String): Int { + val parts = time.split(":") + if (parts.size != 2) return 0 + return (parts[0].toIntOrNull() ?: 0) * 60 + (parts[1].toIntOrNull() ?: 0) + } +} diff --git a/modules/widget/android/src/main/java/dev/tokenteam/iwut/widget/ScheduleWidget.kt b/modules/widget/android/src/main/java/dev/tokenteam/iwut/widget/ScheduleWidget.kt new file mode 100644 index 0000000..eb94c4f --- /dev/null +++ b/modules/widget/android/src/main/java/dev/tokenteam/iwut/widget/ScheduleWidget.kt @@ -0,0 +1,163 @@ +package dev.tokenteam.iwut.widget + +import android.app.AlarmManager +import android.app.PendingIntent +import android.appwidget.AppWidgetManager +import android.appwidget.AppWidgetProvider +import android.content.Context +import android.content.Intent +import android.view.View +import android.widget.RemoteViews +import java.util.Calendar + +class ScheduleWidget : AppWidgetProvider() { + + override fun onUpdate( + context: Context, + appWidgetManager: AppWidgetManager, + appWidgetIds: IntArray, + ) { + for (id in appWidgetIds) { + updateWidget(context, appWidgetManager, id) + } + scheduleNextAlarm(context) + } + + override fun onReceive(context: Context, intent: Intent) { + super.onReceive(context, intent) + if (intent.action == ACTION_AUTO_REFRESH) { + val manager = AppWidgetManager.getInstance(context) + val ids = manager.getAppWidgetIds( + android.content.ComponentName(context, ScheduleWidget::class.java) + ) + onUpdate(context, manager, ids) + } + } + + companion object { + const val ACTION_AUTO_REFRESH = "dev.tokenteam.iwut.widget.AUTO_REFRESH" + + fun updateWidget( + context: Context, + appWidgetManager: AppWidgetManager, + appWidgetId: Int, + ) { + val views = RemoteViews(context.packageName, R.layout.widget_schedule) + val data = ScheduleData.load(context) + + if (data == null || data.termStart.isEmpty()) { + views.setViewVisibility(R.id.course_group, View.GONE) + views.setViewVisibility(R.id.all_done_group, View.VISIBLE) + appWidgetManager.updateAppWidget(appWidgetId, views) + return + } + + val week = ScheduleData.getCurrentWeek(data.termStart) + val today = ScheduleData.getDayOfWeek() + val tomorrowDay = ScheduleData.getTomorrowDayOfWeek() + val tomorrowWeek = ScheduleData.getTomorrowWeek(data.termStart) + + views.setTextViewText(R.id.tv_week, ScheduleData.getWeekStr(week)) + views.setTextViewText(R.id.tv_date, ScheduleData.getDateStr()) + views.setTextViewText(R.id.tv_day_of_week, ScheduleData.getDayOfWeekStr(today)) + + val now = Calendar.getInstance() + val nowMin = now.get(Calendar.HOUR_OF_DAY) * 60 + now.get(Calendar.MINUTE) + + val todayCourses = data.courses + .filter { it.day == today && it.weekStart <= week && it.weekEnd >= week } + .sortedBy { it.sectionStart } + + val tomorrowCourses = data.courses + .filter { it.day == tomorrowDay && it.weekStart <= tomorrowWeek && it.weekEnd >= tomorrowWeek } + .sortedBy { it.sectionStart } + + val upcomingToday = todayCourses.filter { + ScheduleData.parseTimeToMinutes(it.endTime) > nowMin + } + + val combined = (upcomingToday.map { it to true } + tomorrowCourses.map { it to false }).take(2) + + if (combined.isEmpty()) { + views.setViewVisibility(R.id.course_group, View.GONE) + views.setViewVisibility(R.id.all_done_group, View.VISIBLE) + appWidgetManager.updateAppWidget(appWidgetId, views) + return + } + + views.setViewVisibility(R.id.course_group, View.VISIBLE) + views.setViewVisibility(R.id.all_done_group, View.GONE) + + val (c1, c1IsToday) = combined[0] + views.setViewVisibility(R.id.course_row_1, View.VISIBLE) + views.setTextViewText(R.id.course_1_name, c1.name) + views.setTextViewText(R.id.course_1_tag, if (c1IsToday) "今天" else "明天") + views.setTextViewText(R.id.course_1_room, c1.room) + views.setTextViewText(R.id.course_1_time, "${c1.startTime}-${c1.endTime}") + + if (combined.size > 1) { + val (c2, c2IsToday) = combined[1] + views.setViewVisibility(R.id.course_row_2, View.VISIBLE) + views.setViewVisibility(R.id.tv_no_more, View.GONE) + views.setTextViewText(R.id.course_2_name, c2.name) + views.setTextViewText(R.id.course_2_tag, if (c2IsToday) "今天" else "明天") + views.setTextViewText(R.id.course_2_room, c2.room) + views.setTextViewText(R.id.course_2_time, "${c2.startTime}-${c2.endTime}") + } else { + views.setViewVisibility(R.id.course_row_2, View.GONE) + views.setViewVisibility(R.id.tv_no_more, View.VISIBLE) + } + + val hintText: String + if (upcomingToday.isEmpty() && tomorrowCourses.isEmpty()) { + hintText = "今天和明天都没有课啦~" + } else { + val todayHint = if (upcomingToday.isEmpty()) "今天没有课啦," else "今天还有${upcomingToday.size}节课," + val tomorrowHint = if (tomorrowCourses.isEmpty()) "明天没有课啦~" else "明天还有${tomorrowCourses.size}节课" + hintText = todayHint + tomorrowHint + } + views.setViewVisibility(R.id.tv_course_hint, View.VISIBLE) + views.setTextViewText(R.id.tv_course_hint, hintText) + + appWidgetManager.updateAppWidget(appWidgetId, views) + } + + fun scheduleNextAlarm(context: Context) { + val data = ScheduleData.load(context) ?: return + if (data.termStart.isEmpty()) return + + val week = ScheduleData.getCurrentWeek(data.termStart) + val today = ScheduleData.getDayOfWeek() + val now = Calendar.getInstance() + val nowMin = now.get(Calendar.HOUR_OF_DAY) * 60 + now.get(Calendar.MINUTE) + + val nextEndMin = data.courses + .filter { it.day == today && it.weekStart <= week && it.weekEnd >= week } + .map { ScheduleData.parseTimeToMinutes(it.endTime) } + .filter { it > nowMin } + .minOrNull() ?: return + + val alarmTime = Calendar.getInstance().apply { + set(Calendar.HOUR_OF_DAY, nextEndMin / 60) + set(Calendar.MINUTE, nextEndMin % 60) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + } + + val intent = Intent(context, ScheduleWidget::class.java).apply { + action = ACTION_AUTO_REFRESH + } + val pendingIntent = PendingIntent.getBroadcast( + context, 0, intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + + val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager + alarmManager.setExactAndAllowWhileIdle( + AlarmManager.RTC_WAKEUP, + alarmTime.timeInMillis, + pendingIntent + ) + } + } +} diff --git a/modules/widget/android/src/main/java/dev/tokenteam/iwut/widget/WidgetModule.kt b/modules/widget/android/src/main/java/dev/tokenteam/iwut/widget/WidgetModule.kt new file mode 100644 index 0000000..dbac449 --- /dev/null +++ b/modules/widget/android/src/main/java/dev/tokenteam/iwut/widget/WidgetModule.kt @@ -0,0 +1,39 @@ +package dev.tokenteam.iwut.widget + +import android.appwidget.AppWidgetManager +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import expo.modules.kotlin.modules.Module +import expo.modules.kotlin.modules.ModuleDefinition + +class WidgetModule : Module() { + override fun definition() = ModuleDefinition { + Name("Widget") + + AsyncFunction("setWidgetData") { key: String, json: String -> + val context = appContext.reactContext ?: return@AsyncFunction null + context + .getSharedPreferences("widget_data", Context.MODE_PRIVATE) + .edit() + .putString(key, json) + .apply() + null + } + + AsyncFunction("reloadWidgets") { + val context = appContext.reactContext ?: return@AsyncFunction null + val manager = AppWidgetManager.getInstance(context) + val widget = ComponentName(context, ScheduleWidget::class.java) + val ids = manager.getAppWidgetIds(widget) + if (ids.isNotEmpty()) { + val intent = Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE) + intent.component = widget + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids) + context.sendBroadcast(intent) + } + ScheduleWidget.scheduleNextAlarm(context) + null + } + } +} diff --git a/modules/widget/android/src/main/res/drawable/ic_widget_schedule_all_done.xml b/modules/widget/android/src/main/res/drawable/ic_widget_schedule_all_done.xml new file mode 100644 index 0000000..36bb90a --- /dev/null +++ b/modules/widget/android/src/main/res/drawable/ic_widget_schedule_all_done.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/widget/android/src/main/res/drawable/ic_widget_schedule_bg.xml b/modules/widget/android/src/main/res/drawable/ic_widget_schedule_bg.xml new file mode 100644 index 0000000..50a4c56 --- /dev/null +++ b/modules/widget/android/src/main/res/drawable/ic_widget_schedule_bg.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/widget/android/src/main/res/drawable/widget_schedule_background.xml b/modules/widget/android/src/main/res/drawable/widget_schedule_background.xml new file mode 100644 index 0000000..f4152d3 --- /dev/null +++ b/modules/widget/android/src/main/res/drawable/widget_schedule_background.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/modules/widget/android/src/main/res/drawable/widget_schedule_date_hint_bg.xml b/modules/widget/android/src/main/res/drawable/widget_schedule_date_hint_bg.xml new file mode 100644 index 0000000..1ce8f9b --- /dev/null +++ b/modules/widget/android/src/main/res/drawable/widget_schedule_date_hint_bg.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/modules/widget/android/src/main/res/drawable/widget_schedule_day_of_week_bg.xml b/modules/widget/android/src/main/res/drawable/widget_schedule_day_of_week_bg.xml new file mode 100644 index 0000000..aadfc2e --- /dev/null +++ b/modules/widget/android/src/main/res/drawable/widget_schedule_day_of_week_bg.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/modules/widget/android/src/main/res/layout/widget_schedule.xml b/modules/widget/android/src/main/res/layout/widget_schedule.xml new file mode 100644 index 0000000..7647fef --- /dev/null +++ b/modules/widget/android/src/main/res/layout/widget_schedule.xml @@ -0,0 +1,299 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/widget/android/src/main/res/values-night/widget_colors.xml b/modules/widget/android/src/main/res/values-night/widget_colors.xml new file mode 100644 index 0000000..0bd8a75 --- /dev/null +++ b/modules/widget/android/src/main/res/values-night/widget_colors.xml @@ -0,0 +1,9 @@ + + + #1D1D20 + #007AFF + #FFFFFF + #AEAEB2 + #FFFFFF + #F3F3F3 + diff --git a/modules/widget/android/src/main/res/values/widget_colors.xml b/modules/widget/android/src/main/res/values/widget_colors.xml new file mode 100644 index 0000000..07d2f99 --- /dev/null +++ b/modules/widget/android/src/main/res/values/widget_colors.xml @@ -0,0 +1,9 @@ + + + #F0F1FF + #007AFF + #48484A + #AEAEB2 + #FFFFFF + #1C1C1E + diff --git a/modules/widget/android/src/main/res/values/widget_dimens.xml b/modules/widget/android/src/main/res/values/widget_dimens.xml new file mode 100644 index 0000000..ead5099 --- /dev/null +++ b/modules/widget/android/src/main/res/values/widget_dimens.xml @@ -0,0 +1,14 @@ + + + 22dp + 17dp + 22dp + 17dp + 77dp + 34dp + 14dp + 12dp + 12dp + 10dp + 17dp + diff --git a/modules/widget/android/src/main/res/values/widget_strings.xml b/modules/widget/android/src/main/res/values/widget_strings.xml new file mode 100644 index 0000000..f01c643 --- /dev/null +++ b/modules/widget/android/src/main/res/values/widget_strings.xml @@ -0,0 +1,5 @@ + + + 没有更多课啦,放松一下吧~ + 没有更多课啦~ + diff --git a/modules/widget/android/src/main/res/xml/widget_schedule_info.xml b/modules/widget/android/src/main/res/xml/widget_schedule_info.xml new file mode 100644 index 0000000..ae87834 --- /dev/null +++ b/modules/widget/android/src/main/res/xml/widget_schedule_info.xml @@ -0,0 +1,8 @@ + + diff --git a/modules/widget/expo-module.config.json b/modules/widget/expo-module.config.json new file mode 100644 index 0000000..b52a51e --- /dev/null +++ b/modules/widget/expo-module.config.json @@ -0,0 +1,9 @@ +{ + "platforms": ["android", "ios"], + "android": { + "modules": ["dev.tokenteam.iwut.widget.WidgetModule"] + }, + "ios": { + "modules": ["WidgetModule"] + } +} diff --git a/modules/widget/index.ts b/modules/widget/index.ts new file mode 100644 index 0000000..bdb931a --- /dev/null +++ b/modules/widget/index.ts @@ -0,0 +1,19 @@ +import { requireNativeModule } from "expo-modules-core"; + +interface WidgetNativeModule { + setWidgetData(key: string, json: string): Promise; + reloadWidgets(): Promise; +} + +const WidgetModule = requireNativeModule("Widget"); + +export async function setWidgetData( + key: string, + data: Record, +): Promise { + await WidgetModule.setWidgetData(key, JSON.stringify(data)); +} + +export async function reloadWidgets(): Promise { + await WidgetModule.reloadWidgets(); +} diff --git a/modules/widget/ios/Widget.podspec b/modules/widget/ios/Widget.podspec new file mode 100644 index 0000000..a46a8f7 --- /dev/null +++ b/modules/widget/ios/Widget.podspec @@ -0,0 +1,19 @@ +Pod::Spec.new do |s| + s.name = 'Widget' + s.version = '1.0.0' + s.summary = '.' + s.homepage = 'https://github.com/tokenteam/iwut' + s.author = 'tokenteam' + s.platforms = { :ios => '15.1' } + s.swift_version = '5.9' + s.source = { git: '' } + s.static_framework = true + + s.dependency 'ExpoModulesCore' + + s.source_files = '**/*.{h,m,swift}' + s.pod_target_xcconfig = { + 'DEFINES_MODULE' => 'YES', + 'SWIFT_COMPILATION_MODE' => 'wholemodule' + } +end diff --git a/modules/widget/ios/WidgetModule.swift b/modules/widget/ios/WidgetModule.swift new file mode 100644 index 0000000..36bc542 --- /dev/null +++ b/modules/widget/ios/WidgetModule.swift @@ -0,0 +1,19 @@ +import ExpoModulesCore +import WidgetKit + +public class WidgetModule: Module { + public func definition() -> ModuleDefinition { + Name("Widget") + + AsyncFunction("setWidgetData") { (key: String, json: String) in + let defaults = UserDefaults(suiteName: "group.dev.tokenteam.iwut") + defaults?.set(json, forKey: key) + } + + AsyncFunction("reloadWidgets") { + if #available(iOS 14.0, *) { + WidgetCenter.shared.reloadAllTimelines() + } + } + } +} diff --git a/package.json b/package.json index 3b39093..629a51f 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "format": "prettier --write ." }, "dependencies": { + "@bacons/apple-targets": "^4.0.6", "@expo/vector-icons": "^15.0.3", "@preeternal/react-native-cookie-manager": "^6.3.2", "@react-native-assets/slider": "^11.0.12", diff --git a/services/get-course.tsx b/services/get-course.tsx index 3637de8..772292d 100644 --- a/services/get-course.tsx +++ b/services/get-course.tsx @@ -22,6 +22,7 @@ import { WebView } from "react-native-webview"; import { IS_DEV } from "@/constants/is-dev"; import { useZhlgdAutoLogin } from "@/hooks/use-zhlgd-autologin"; import { reportError } from "@/lib/report"; +import { syncWidgetData } from "@/services/widget-sync"; import { type Course, type ImportType, useCourseStore } from "@/store/course"; // 本科生 @@ -352,6 +353,7 @@ export const GetCourse = forwardRef( store.setImportedCourses(courses); store.setLastImportType(importType); if (msg.termStart) store.setTermStart(msg.termStart); + syncWidgetData().catch(() => {}); finish(true); return; } @@ -377,6 +379,7 @@ export const GetCourse = forwardRef( const store = useCourseStore.getState(); store.setImportedCourses(courses); store.setLastImportType(importType); + syncWidgetData().catch(() => {}); finish(true); } }, diff --git a/services/widget-sync.ts b/services/widget-sync.ts new file mode 100644 index 0000000..58084ef --- /dev/null +++ b/services/widget-sync.ts @@ -0,0 +1,47 @@ +import { reloadWidgets, setWidgetData } from "@/modules/widget"; +import { SECTION_TIMES } from "@/services/course-time"; +import { useCourseStore } from "@/store/course"; + +interface WidgetCourse { + name: string; + room: string; + day: number; + weekStart: number; + weekEnd: number; + sectionStart: number; + sectionEnd: number; + startTime: string; + endTime: string; +} + +interface ScheduleWidgetData { + courses: WidgetCourse[]; + termStart: string; + updatedAt: string; +} + +export async function syncWidgetData(): Promise { + const { courses, termStart } = useCourseStore.getState(); + if (!termStart || courses.length === 0) return; + + const widgetCourses: WidgetCourse[] = courses.map((c) => ({ + name: c.name, + room: c.room, + day: c.day, + weekStart: c.weekStart, + weekEnd: c.weekEnd, + sectionStart: c.sectionStart, + sectionEnd: c.sectionEnd, + startTime: SECTION_TIMES[c.sectionStart]?.[0] ?? "", + endTime: SECTION_TIMES[c.sectionEnd]?.[1] ?? "", + })); + + const data: ScheduleWidgetData = { + courses: widgetCourses, + termStart, + updatedAt: new Date().toISOString(), + }; + + await setWidgetData("schedule", data as unknown as Record); + await reloadWidgets(); +} diff --git a/targets/widget/Assets.xcassets/AccentBlue.colorset/Contents.json b/targets/widget/Assets.xcassets/AccentBlue.colorset/Contents.json new file mode 100644 index 0000000..df4ba6d --- /dev/null +++ b/targets/widget/Assets.xcassets/AccentBlue.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors": [ + { + "color": { + "color-space": "srgb", + "components": { + "alpha": "1.000", + "blue": "1.000", + "green": "0.478", + "red": "0.000" + } + }, + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/targets/widget/Assets.xcassets/BackgroundLogo.imageset/BackgroundLogo.svg b/targets/widget/Assets.xcassets/BackgroundLogo.imageset/BackgroundLogo.svg new file mode 100644 index 0000000..67bdb66 --- /dev/null +++ b/targets/widget/Assets.xcassets/BackgroundLogo.imageset/BackgroundLogo.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/targets/widget/Assets.xcassets/BackgroundLogo.imageset/Contents.json b/targets/widget/Assets.xcassets/BackgroundLogo.imageset/Contents.json new file mode 100644 index 0000000..d25393c --- /dev/null +++ b/targets/widget/Assets.xcassets/BackgroundLogo.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images": [ + { + "filename": "BackgroundLogo.svg", + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + }, + "properties": { + "preserves-vector-representation": true + } +} diff --git a/targets/widget/Assets.xcassets/Contents.json b/targets/widget/Assets.xcassets/Contents.json new file mode 100644 index 0000000..74d6a72 --- /dev/null +++ b/targets/widget/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/targets/widget/Assets.xcassets/EmptyCourseImage.imageset/Contents.json b/targets/widget/Assets.xcassets/EmptyCourseImage.imageset/Contents.json new file mode 100644 index 0000000..163257d --- /dev/null +++ b/targets/widget/Assets.xcassets/EmptyCourseImage.imageset/Contents.json @@ -0,0 +1,25 @@ +{ + "images": [ + { + "filename": "EmptyCourseImage.svg", + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "filename": "EmptyCourseImage_dark.svg", + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + }, + "properties": { + "preserves-vector-representation": true + } +} diff --git a/targets/widget/Assets.xcassets/EmptyCourseImage.imageset/EmptyCourseImage.svg b/targets/widget/Assets.xcassets/EmptyCourseImage.imageset/EmptyCourseImage.svg new file mode 100644 index 0000000..951963f --- /dev/null +++ b/targets/widget/Assets.xcassets/EmptyCourseImage.imageset/EmptyCourseImage.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/targets/widget/Assets.xcassets/EmptyCourseImage.imageset/EmptyCourseImage_dark.svg b/targets/widget/Assets.xcassets/EmptyCourseImage.imageset/EmptyCourseImage_dark.svg new file mode 100644 index 0000000..ef2a0b6 --- /dev/null +++ b/targets/widget/Assets.xcassets/EmptyCourseImage.imageset/EmptyCourseImage_dark.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/targets/widget/Assets.xcassets/TextPrimary.colorset/Contents.json b/targets/widget/Assets.xcassets/TextPrimary.colorset/Contents.json new file mode 100644 index 0000000..db3ea36 --- /dev/null +++ b/targets/widget/Assets.xcassets/TextPrimary.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors": [ + { + "color": { + "color-space": "srgb", + "components": { + "alpha": "1.000", + "blue": "0.290", + "green": "0.282", + "red": "0.282" + } + }, + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "alpha": "1.000", + "blue": "1.000", + "green": "1.000", + "red": "1.000" + } + }, + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/targets/widget/Assets.xcassets/TextSecondary.colorset/Contents.json b/targets/widget/Assets.xcassets/TextSecondary.colorset/Contents.json new file mode 100644 index 0000000..b71d4ca --- /dev/null +++ b/targets/widget/Assets.xcassets/TextSecondary.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors": [ + { + "color": { + "color-space": "srgb", + "components": { + "alpha": "1.000", + "blue": "0.698", + "green": "0.682", + "red": "0.682" + } + }, + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "alpha": "1.000", + "blue": "0.698", + "green": "0.682", + "red": "0.682" + } + }, + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/targets/widget/Assets.xcassets/WidgetBackground.colorset/Contents.json b/targets/widget/Assets.xcassets/WidgetBackground.colorset/Contents.json new file mode 100644 index 0000000..248a8e1 --- /dev/null +++ b/targets/widget/Assets.xcassets/WidgetBackground.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors": [ + { + "color": { + "color-space": "srgb", + "components": { + "alpha": "1.000", + "blue": "1.000", + "green": "0.945", + "red": "0.941" + } + }, + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "alpha": "1.000", + "blue": "0.125", + "green": "0.114", + "red": "0.114" + } + }, + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/targets/widget/Info.plist b/targets/widget/Info.plist new file mode 100644 index 0000000..5510804 --- /dev/null +++ b/targets/widget/Info.plist @@ -0,0 +1,11 @@ + + + + + NSExtension + + NSExtensionPointIdentifier + com.apple.widgetkit-extension + + + \ No newline at end of file diff --git a/targets/widget/Models/WidgetData.swift b/targets/widget/Models/WidgetData.swift new file mode 100644 index 0000000..5dad51c --- /dev/null +++ b/targets/widget/Models/WidgetData.swift @@ -0,0 +1,109 @@ +import Foundation + +struct WidgetCourse: Codable { + let name: String + let room: String + let day: Int + let weekStart: Int + let weekEnd: Int + let sectionStart: Int + let sectionEnd: Int + let startTime: String + let endTime: String +} + +struct ScheduleWidgetData: Codable { + let courses: [WidgetCourse] + let termStart: String + let updatedAt: String + + static func load() -> ScheduleWidgetData? { + guard let defaults = UserDefaults(suiteName: "group.dev.tokenteam.iwut"), + let json = defaults.string(forKey: "schedule"), + let data = json.data(using: .utf8) + else { return nil } + + return try? JSONDecoder().decode(ScheduleWidgetData.self, from: data) + } +} + +struct ScheduleHelper { + private static let dayNames = ["", "周一", "周二", "周三", "周四", "周五", "周六", "周日"] + + static func currentWeek(termStart: String) -> Int { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd" + formatter.locale = Locale(identifier: "en_US_POSIX") + guard let startDate = formatter.date(from: termStart) else { return 1 } + + let now = Date() + let diffSeconds = now.timeIntervalSince(startDate) + if diffSeconds < 0 { return 0 } + let diffDays = Int(diffSeconds / 86400) + return diffDays / 7 + 1 + } + + static func dayOfWeek(for date: Date = .now) -> Int { + let weekday = Calendar.current.component(.weekday, from: date) + return weekday == 1 ? 7 : weekday - 1 + } + + static func tomorrowDayOfWeek() -> Int { + let today = dayOfWeek() + return today == 7 ? 1 : today + 1 + } + + static func tomorrowWeek(termStart: String) -> Int { + let today = dayOfWeek() + let week = currentWeek(termStart: termStart) + return today == 7 ? week + 1 : week + } + + static func weekStr(week: Int) -> String { + "第\(week)周" + } + + static func dateStr(for date: Date = .now) -> String { + let cal = Calendar.current + let month = cal.component(.month, from: date) + let day = cal.component(.day, from: date) + return "\(month)月\(day)日" + } + + static func dayOfWeekStr(day: Int) -> String { + guard day >= 1 && day <= 7 else { return "" } + return dayNames[day] + } + + static func parseTimeToMinutes(_ time: String) -> Int { + let parts = time.split(separator: ":") + guard parts.count == 2, + let hour = Int(parts[0]), + let minute = Int(parts[1]) else { return 0 } + return hour * 60 + minute + } + + static func todayCourses(from data: ScheduleWidgetData) -> [WidgetCourse] { + let week = currentWeek(termStart: data.termStart) + let today = dayOfWeek() + return data.courses + .filter { $0.day == today && $0.weekStart <= week && $0.weekEnd >= week } + .sorted { $0.sectionStart < $1.sectionStart } + } + + static func tomorrowCourses(from data: ScheduleWidgetData) -> [WidgetCourse] { + let tWeek = tomorrowWeek(termStart: data.termStart) + let tDay = tomorrowDayOfWeek() + return data.courses + .filter { $0.day == tDay && $0.weekStart <= tWeek && $0.weekEnd >= tWeek } + .sorted { $0.sectionStart < $1.sectionStart } + } + + static func upcomingTodayCourses(from data: ScheduleWidgetData) -> [WidgetCourse] { + let cal = Calendar.current + let nowMin = cal.component(.hour, from: .now) * 60 + cal.component(.minute, from: .now) + return todayCourses(from: data).filter { + parseTimeToMinutes($0.endTime) > nowMin + } + } +} diff --git a/targets/widget/ScheduleTimelineProvider.swift b/targets/widget/ScheduleTimelineProvider.swift new file mode 100644 index 0000000..92de3b3 --- /dev/null +++ b/targets/widget/ScheduleTimelineProvider.swift @@ -0,0 +1,76 @@ +import WidgetKit + +struct ScheduleEntry: TimelineEntry { + let date: Date + let data: ScheduleWidgetData? + + var upcomingToday: [WidgetCourse] { + guard let data = data else { return [] } + return ScheduleHelper.upcomingTodayCourses(from: data) + } + + var tomorrowCourses: [WidgetCourse] { + guard let data = data else { return [] } + return ScheduleHelper.tomorrowCourses(from: data) + } + + var displayCourses: [(course: WidgetCourse, isToday: Bool)] { + let today = upcomingToday.map { ($0, true) } + let tomorrow = tomorrowCourses.map { ($0, false) } + return Array((today + tomorrow).prefix(2)) + } + + var weekStr: String { + guard let data = data else { return "第-周" } + return ScheduleHelper.weekStr(week: ScheduleHelper.currentWeek(termStart: data.termStart)) + } + + var dateStr: String { + ScheduleHelper.dateStr(for: date) + } + + var dayOfWeekStr: String { + ScheduleHelper.dayOfWeekStr(day: ScheduleHelper.dayOfWeek(for: date)) + } +} + +struct ScheduleTimelineProvider: TimelineProvider { + func placeholder(in context: Context) -> ScheduleEntry { + ScheduleEntry(date: .now, data: nil) + } + + func getSnapshot(in context: Context, completion: @escaping (ScheduleEntry) -> Void) { + let entry = ScheduleEntry(date: .now, data: ScheduleWidgetData.load()) + completion(entry) + } + + func getTimeline(in context: Context, completion: @escaping (Timeline) -> Void) { + let data = ScheduleWidgetData.load() + var entries: [ScheduleEntry] = [] + + entries.append(ScheduleEntry(date: .now, data: data)) + + if let data = data { + let todayCourses = ScheduleHelper.todayCourses(from: data) + let calendar = Calendar.current + + for course in todayCourses { + let parts = course.endTime.split(separator: ":") + guard parts.count == 2, + let hour = Int(parts[0]), + let minute = Int(parts[1]), + let endDate = calendar.date(bySettingHour: hour, minute: minute, second: 0, of: .now), + endDate > .now + else { continue } + entries.append(ScheduleEntry(date: endDate, data: data)) + } + } + + let nextMidnight = Calendar.current.startOfDay( + for: Calendar.current.date(byAdding: .day, value: 1, to: .now) ?? .now + ) + entries.append(ScheduleEntry(date: nextMidnight, data: data)) + + completion(Timeline(entries: entries, policy: .after(nextMidnight))) + } +} diff --git a/targets/widget/ScheduleWidget.swift b/targets/widget/ScheduleWidget.swift new file mode 100644 index 0000000..9082d29 --- /dev/null +++ b/targets/widget/ScheduleWidget.swift @@ -0,0 +1,22 @@ +import SwiftUI +import WidgetKit + +struct ScheduleWidget: Widget { + let kind: String = "ScheduleWidget" + + var body: some WidgetConfiguration { + StaticConfiguration(kind: kind, provider: ScheduleTimelineProvider()) { entry in + if #available(iOS 17.0, *) { + ScheduleWidgetEntryView(entry: entry) + .containerBackground(Color("WidgetBackground"), for: .widget) + } else { + ScheduleWidgetEntryView(entry: entry) + .background(Color("WidgetBackground")) + } + } + .contentMarginsDisabled() + .configurationDisplayName("课程表") + .description("今天有什么课?看这里就够啦~") + .supportedFamilies([.systemMedium]) + } +} diff --git a/targets/widget/Views/BackgroundView.swift b/targets/widget/Views/BackgroundView.swift new file mode 100644 index 0000000..8e1cf5e --- /dev/null +++ b/targets/widget/Views/BackgroundView.swift @@ -0,0 +1,34 @@ +import SwiftUI + +struct WidgetBackgroundView: View { + let isEmpty: Bool + + var body: some View { + VStack { + Spacer() + HStack { + Image("BackgroundLogo") + .renderingMode(.original) + .resizable() + .scaledToFit() + .frame(height: 120) + .offset(x: -5, y: 10) + Spacer() + } + } + + if isEmpty { + VStack { + Spacer() + HStack { + Spacer() + Image("EmptyCourseImage") + .renderingMode(.original) + .resizable() + .frame(width: 220, height: 110) + .offset(y: 1) + } + } + } + } +} diff --git a/targets/widget/Views/CourseInfoView.swift b/targets/widget/Views/CourseInfoView.swift new file mode 100644 index 0000000..45d855b --- /dev/null +++ b/targets/widget/Views/CourseInfoView.swift @@ -0,0 +1,54 @@ +import SwiftUI + +struct CourseInfoView: View { + let course: WidgetCourse + let isToday: Bool + + private var tagText: String { + isToday ? "今天" : "明天" + } + + var body: some View { + VStack(alignment: .leading, spacing: 4) { + HStack(spacing: 6) { + Text(course.name) + .font(.system(size: 14)) + .lineLimit(1) + .truncationMode(.tail) + .foregroundColor(Color("TextPrimary")) + + Text(tagText) + .font(.system(size: 10)) + .foregroundColor(Color("AccentBlue")) + .fontWeight(.bold) + .padding(.horizontal, 4) + .padding(.vertical, 2) + .background( + RoundedRectangle(cornerRadius: 4) + .stroke(Color("AccentBlue"), lineWidth: 1) + ) + } + + HStack(alignment: .bottom, spacing: 0) { + Text(course.room.isEmpty ? "暂无教室信息" : course.room) + .font(.system(size: 12)) + .lineLimit(2) + .foregroundColor(Color("TextSecondary")) + .fixedSize(horizontal: false, vertical: true) + + Spacer(minLength: 4) + + Text("|") + .font(.system(size: 12)) + .foregroundColor(Color("TextSecondary")) + .padding(.trailing, 4) + + Text("\(course.startTime)-\(course.endTime)") + .foregroundColor(Color("TextSecondary")) + .lineLimit(1) + .font(.system(size: 12).monospacedDigit()) + .fixedSize(horizontal: true, vertical: false) + } + } + } +} diff --git a/targets/widget/Views/DateInfoView.swift b/targets/widget/Views/DateInfoView.swift new file mode 100644 index 0000000..b8e4224 --- /dev/null +++ b/targets/widget/Views/DateInfoView.swift @@ -0,0 +1,30 @@ +import SwiftUI + +struct DateInfoView: View { + let weekStr: String + let dateStr: String + let dayOfWeekStr: String + + var body: some View { + VStack(alignment: .leading, spacing: 0) { + Text(weekStr) + .foregroundColor(Color("AccentBlue")) + .font(.system(size: 14)) + .padding(.bottom, 4) + + Text(dateStr) + .foregroundColor(Color("TextPrimary")) + .font(.system(size: 18)) + + Spacer() + + ZStack { + RoundedRectangle(cornerRadius: 12) + .foregroundColor(Color("AccentBlue")) + Text(dayOfWeekStr) + .foregroundColor(.white) + .font(.system(size: 14)) + }.frame(width: 64, height: 28) + } + } +} diff --git a/targets/widget/Views/EmptyCourseView.swift b/targets/widget/Views/EmptyCourseView.swift new file mode 100644 index 0000000..058e233 --- /dev/null +++ b/targets/widget/Views/EmptyCourseView.swift @@ -0,0 +1,13 @@ +import SwiftUI + +struct EmptyCourseView: View { + var body: some View { + HStack(spacing: 0) { + Text("没有更多课啦,放松一下吧~") + .foregroundColor(Color("TextPrimary")) + .font(.system(size: 14)) + .padding(.leading, 8) + Spacer() + } + } +} diff --git a/targets/widget/Views/ScheduleWidgetEntryView.swift b/targets/widget/Views/ScheduleWidgetEntryView.swift new file mode 100644 index 0000000..84b34b9 --- /dev/null +++ b/targets/widget/Views/ScheduleWidgetEntryView.swift @@ -0,0 +1,83 @@ +import SwiftUI +import WidgetKit + +struct ScheduleWidgetEntryView: View { + var entry: ScheduleTimelineProvider.Entry + + private var displayCourses: [(course: WidgetCourse, isToday: Bool)] { + entry.displayCourses + } + + private var bottomText: String { + guard entry.data != nil else { return "" } + if displayCourses.isEmpty { return "" } + + let upcomingCount = entry.upcomingToday.count + let tomorrowCount = entry.tomorrowCourses.count + + if upcomingCount == 0 && tomorrowCount == 0 { + return "今天和明天都没有课啦~" + } + + let todayPart: String + if upcomingCount == 0 { + todayPart = "今天没有课啦," + } else { + todayPart = "今天还有\(upcomingCount)节课," + } + + let tomorrowPart: String + if tomorrowCount == 0 { + tomorrowPart = "明天没有课啦~" + } else { + tomorrowPart = "明天还有\(tomorrowCount)节课" + } + + return todayPart + tomorrowPart + } + + var body: some View { + ZStack { + WidgetBackgroundView(isEmpty: displayCourses.isEmpty) + + HStack(spacing: 12) { + DateInfoView( + weekStr: entry.weekStr, + dateStr: entry.dateStr, + dayOfWeekStr: entry.dayOfWeekStr + ) + + VStack(alignment: .leading, spacing: 0) { + if displayCourses.isEmpty { + EmptyCourseView() + } + + ForEach(displayCourses.indices, id: \.self) { index in + if index > 0 { + Divider() + .padding(.vertical, 8) + } + let item = displayCourses[index] + CourseInfoView(course: item.course, isToday: item.isToday) + } + + if displayCourses.count == 1 { + Text("没有更多课啦~") + .foregroundColor(Color("TextPrimary")) + .font(.system(size: 12)) + .padding(.top, 8) + } + + Spacer() + + if !bottomText.isEmpty { + Text(bottomText) + .foregroundColor(Color("TextSecondary")) + .font(.system(size: 12)) + } + } + } + .padding(16) + } + } +} diff --git a/targets/widget/WidgetBundle.swift b/targets/widget/WidgetBundle.swift new file mode 100644 index 0000000..d6f7e7a --- /dev/null +++ b/targets/widget/WidgetBundle.swift @@ -0,0 +1,9 @@ +import SwiftUI +import WidgetKit + +@main +struct IwutWidgetBundle: WidgetBundle { + var body: some Widget { + ScheduleWidget() + } +} diff --git a/targets/widget/expo-target.config.js b/targets/widget/expo-target.config.js new file mode 100644 index 0000000..42e5800 --- /dev/null +++ b/targets/widget/expo-target.config.js @@ -0,0 +1,9 @@ +/** @type {import('@bacons/apple-targets').Config} */ +module.exports = { + type: "widget", + name: "ScheduleWidget", + deploymentTarget: "17.0", + entitlements: { + "com.apple.security.application-groups": ["group.dev.tokenteam.iwut"], + }, +}; diff --git a/targets/widget/generated.entitlements b/targets/widget/generated.entitlements new file mode 100644 index 0000000..01b4822 --- /dev/null +++ b/targets/widget/generated.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.dev.tokenteam.iwut + + + \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 4dd4121..e171f0d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -794,6 +794,25 @@ "@babel/helper-string-parser" "^7.27.1" "@babel/helper-validator-identifier" "^7.28.5" +"@bacons/apple-targets@^4.0.6": + version "4.0.6" + resolved "https://registry.npmmirror.com/@bacons/apple-targets/-/apple-targets-4.0.6.tgz#1cca86affa754cd4be0565bbc1d1345a1e97a693" + integrity sha512-aDSXI6HPgsroPMOAlNvVyvM2TvU25hgGu/UUWD6XWfWIX4nhGchCSaoEGx7cB3xO+ClQYdzlIAIlZScX6lHBwA== + dependencies: + "@bacons/xcode" "1.0.0-alpha.32" + "@react-native/normalize-colors" "^0.79.2" + debug "^4.3.4" + glob "^10.4.2" + +"@bacons/xcode@1.0.0-alpha.32": + version "1.0.0-alpha.32" + resolved "https://registry.npmmirror.com/@bacons/xcode/-/xcode-1.0.0-alpha.32.tgz#3b49a711472f433d4ece3f157a523e0db39d8987" + integrity sha512-OGpH7+yMbWC2cgYZon5B+VVadH9HsB2V/abtEiplA65XnSuV4GAYAVixOCDc5k182WkfoakfdM0zW6U9cbcsbw== + dependencies: + "@expo/plist" "^0.0.18" + debug "^4.3.4" + uuid "^8.3.2" + "@egjs/hammerjs@^2.0.17": version "2.0.17" resolved "https://registry.npmmirror.com/@egjs/hammerjs/-/hammerjs-2.0.17.tgz#5dc02af75a6a06e4c2db0202cae38c9263895124" @@ -1165,6 +1184,15 @@ ora "^3.4.0" resolve-workspace-root "^2.0.0" +"@expo/plist@^0.0.18": + version "0.0.18" + resolved "https://registry.npmmirror.com/@expo/plist/-/plist-0.0.18.tgz#9abcde78df703a88f6d9fa1a557ee2f045d178b0" + integrity sha512-+48gRqUiz65R21CZ/IXa7RNBXgAI/uPSdvJqoN9x1hfL44DNbUoWHgHiEXTx7XelcATpDwNTz6sHLfy0iNqf+w== + dependencies: + "@xmldom/xmldom" "~0.7.0" + base64-js "^1.2.3" + xmlbuilder "^14.0.0" + "@expo/plist@^0.5.2": version "0.5.2" resolved "https://registry.npmmirror.com/@expo/plist/-/plist-0.5.2.tgz#5bfc81cf09c1c0513a31d7e5cabf85b2ac4d1d71" @@ -1278,6 +1306,18 @@ resolved "https://registry.npmmirror.com/@humanwhocodes/retry/-/retry-0.4.3.tgz#c2b9d2e374ee62c586d3adbea87199b1d7a7a6ba" integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.npmmirror.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + dependencies: + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" + "@isaacs/ttlcache@^1.4.1": version "1.4.1" resolved "https://registry.npmmirror.com/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz#21fb23db34e9b6220c6ba023a0118a2dd3461ea2" @@ -1431,6 +1471,11 @@ resolved "https://registry.npmmirror.com/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz#3dc35ba0f1e66b403c00b39344f870298ebb1c8e" integrity sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA== +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.npmmirror.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + "@preeternal/react-native-cookie-manager@^6.3.2": version "6.3.2" resolved "https://registry.npmmirror.com/@preeternal/react-native-cookie-manager/-/react-native-cookie-manager-6.3.2.tgz#7178469b483c7f43e7f12891ba11860499d4600b" @@ -1757,6 +1802,11 @@ resolved "https://registry.npmmirror.com/@react-native/normalize-colors/-/normalize-colors-0.83.6.tgz#9fef0e98733d58267aecafede08ebcc830a29c82" integrity sha512-bTM24b5v4qN3h52oflnv+OujFORn/kVi06WaWhnQQw14/ycilPqIsqsa+DpIBqdBrXxvLa9fXtCRrQtGATZCEw== +"@react-native/normalize-colors@^0.79.2": + version "0.79.7" + resolved "https://registry.npmmirror.com/@react-native/normalize-colors/-/normalize-colors-0.79.7.tgz#f7a3680dc81528b19761169cb6177ce64638b9ce" + integrity sha512-RrvewhdanEWhlyrHNWGXGZCc6MY0JGpNgRzA8y6OomDz0JmlnlIsbBHbNpPnIrt9Jh2KaV10KTscD1Ry8xU9gQ== + "@react-native/virtualized-lists@0.83.6": version "0.83.6" resolved "https://registry.npmmirror.com/@react-native/virtualized-lists/-/virtualized-lists-0.83.6.tgz#db5c2a7280519c6bea6c77a84cd70cd106a79f87" @@ -2433,6 +2483,11 @@ resolved "https://registry.npmmirror.com/@xmldom/xmldom/-/xmldom-0.9.10.tgz#a0ad5a26fe8aa996310870726e1704977f769dee" integrity sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw== +"@xmldom/xmldom@~0.7.0": + version "0.7.13" + resolved "https://registry.npmmirror.com/@xmldom/xmldom/-/xmldom-0.7.13.tgz#ff34942667a4e19a9f4a0996a76814daac364cf3" + integrity sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g== + abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.npmmirror.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" @@ -2510,6 +2565,11 @@ ansi-regex@^5.0.0, ansi-regex@^5.0.1: resolved "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== +ansi-regex@^6.2.2: + version "6.2.2" + resolved "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.2.2.tgz#60216eea464d864597ce2832000738a0589650c1" + integrity sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg== + ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -2529,6 +2589,11 @@ ansi-styles@^5.0.0: resolved "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== +ansi-styles@^6.1.0: + version "6.2.3" + resolved "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.3.tgz#c044d5dcc521a076413472597a1acb1f103c4041" + integrity sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg== + anymatch@^3.0.3: version "3.1.3" resolved "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" @@ -2845,7 +2910,7 @@ balanced-match@^4.0.2: resolved "https://registry.npmmirror.com/balanced-match/-/balanced-match-4.0.4.tgz#bfb10662feed8196a2c62e7c68e17720c274179a" integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA== -base64-js@^1.3.1, base64-js@^1.5.1: +base64-js@^1.2.3, base64-js@^1.3.1, base64-js@^1.5.1: version "1.5.1" resolved "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== @@ -2896,6 +2961,13 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +brace-expansion@^2.0.2: + version "2.1.0" + resolved "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.1.0.tgz#4f41a41190216ee36067ec381526fe9539c4f0ae" + integrity sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w== + dependencies: + balanced-match "^1.0.0" + brace-expansion@^5.0.5: version "5.0.5" resolved "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-5.0.5.tgz#dcc3a37116b79f3e1b46db994ced5d570e930fdb" @@ -3340,6 +3412,11 @@ dunder-proto@^1.0.0, dunder-proto@^1.0.1: es-errors "^1.3.0" gopd "^1.2.0" +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.npmmirror.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + ee-first@1.1.1: version "1.1.1" resolved "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -3355,6 +3432,11 @@ emoji-regex@^8.0.0: resolved "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.npmmirror.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -4183,6 +4265,14 @@ for-each@^0.3.3, for-each@^0.3.5: dependencies: is-callable "^1.2.7" +foreground-child@^3.1.0: + version "3.3.1" + resolved "https://registry.npmmirror.com/foreground-child/-/foreground-child-3.3.1.tgz#32e8e9ed1b68a3497befb9ac2b6adf92a638576f" + integrity sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw== + dependencies: + cross-spawn "^7.0.6" + signal-exit "^4.0.1" + fresh@~0.5.2: version "0.5.2" resolved "https://registry.npmmirror.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" @@ -4297,6 +4387,18 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" +glob@^10.4.2: + version "10.5.0" + resolved "https://registry.npmmirror.com/glob/-/glob-10.5.0.tgz#8ec0355919cd3338c28428a23d4f24ecc5fe738c" + integrity sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg== + dependencies: + foreground-child "^3.1.0" + jackspeak "^3.1.2" + minimatch "^9.0.4" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^1.11.1" + glob@^13.0.0: version "13.0.6" resolved "https://registry.npmmirror.com/glob/-/glob-13.0.6.tgz#078666566a425147ccacfbd2e332deb66a2be71d" @@ -4808,6 +4910,15 @@ iterator.prototype@^1.1.5: has-symbols "^1.1.0" set-function-name "^2.0.2" +jackspeak@^3.1.2: + version "3.4.3" + resolved "https://registry.npmmirror.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" + integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + jest-environment-node@^29.7.0: version "29.7.0" resolved "https://registry.npmmirror.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" @@ -5150,7 +5261,7 @@ loose-envify@^1.0.0, loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" -lru-cache@^10.0.1: +lru-cache@^10.0.1, lru-cache@^10.2.0: version "10.4.3" resolved "https://registry.npmmirror.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== @@ -5449,12 +5560,19 @@ minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2, minimatch@^3.1.5: dependencies: brace-expansion "^1.1.7" +minimatch@^9.0.4: + version "9.0.9" + resolved "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.9.tgz#9b0cb9fcb78087f6fd7eababe2511c4d3d60574e" + integrity sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg== + dependencies: + brace-expansion "^2.0.2" + minimist@^1.2.0, minimist@^1.2.6: version "1.2.8" resolved "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== -minipass@^7.1.2, minipass@^7.1.3: +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2, minipass@^7.1.3: version "7.1.3" resolved "https://registry.npmmirror.com/minipass/-/minipass-7.1.3.tgz#79389b4eb1bb2d003a9bba87d492f2bd37bdc65b" integrity sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A== @@ -5757,6 +5875,11 @@ p-try@^2.0.0: resolved "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +package-json-from-dist@^1.0.0: + version "1.0.1" + resolved "https://registry.npmmirror.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" + integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== + pako@~1.0.2: version "1.0.11" resolved "https://registry.npmmirror.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" @@ -5801,6 +5924,14 @@ path-parse@^1.0.7: resolved "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== +path-scurry@^1.11.1: + version "1.11.1" + resolved "https://registry.npmmirror.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" + integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== + dependencies: + lru-cache "^10.2.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + path-scurry@^2.0.2: version "2.0.2" resolved "https://registry.npmmirror.com/path-scurry/-/path-scurry-2.0.2.tgz#6be0d0ee02a10d9e0de7a98bae65e182c9061f85" @@ -6547,6 +6678,11 @@ signal-exit@^3.0.2, signal-exit@^3.0.7: resolved "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.npmmirror.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + simple-plist@^1.1.0: version "1.3.1" resolved "https://registry.npmmirror.com/simple-plist/-/simple-plist-1.3.1.tgz#16e1d8f62c6c9b691b8383127663d834112fb017" @@ -6663,6 +6799,15 @@ strict-uri-encode@^2.0.0: resolved "https://registry.npmmirror.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" integrity sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ== +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" @@ -6672,6 +6817,15 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.npmmirror.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + string.prototype.matchall@^4.0.12: version "4.0.12" resolved "https://registry.npmmirror.com/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz#6c88740e49ad4956b1332a911e949583a275d4c0" @@ -6738,6 +6892,13 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^5.2.0: version "5.2.0" resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" @@ -6752,6 +6913,13 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" +strip-ansi@^7.0.1: + version "7.2.0" + resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.2.0.tgz#d22a269522836a627af8d04b5c3fd2c7fa3e32e3" + integrity sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w== + dependencies: + ansi-regex "^6.2.2" + strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.npmmirror.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" @@ -7116,6 +7284,11 @@ uuid@^7.0.3: resolved "https://registry.npmmirror.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b" integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg== +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + validate-npm-package-name@^5.0.0: version "5.0.1" resolved "https://registry.npmmirror.com/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz#a316573e9b49f3ccd90dbb6eb52b3f06c6d604e8" @@ -7245,6 +7418,15 @@ word-wrap@^1.2.5: resolved "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" @@ -7254,6 +7436,15 @@ wrap-ansi@^7.0.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + wrappy@1: version "1.0.2" resolved "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -7293,6 +7484,11 @@ xml2js@0.6.0: sax ">=0.6.0" xmlbuilder "~11.0.0" +xmlbuilder@^14.0.0: + version "14.0.0" + resolved "https://registry.npmmirror.com/xmlbuilder/-/xmlbuilder-14.0.0.tgz#876b5aec4f05ffd5feb97b0a871c855d16fbeb8c" + integrity sha512-ts+B2rSe4fIckR6iquDjsKbQFK2NlUk6iG5nf14mDEyldgoc2nEKZ3jZWMPTxGQwVgToSjt6VGIho1H8/fNFTg== + xmlbuilder@^15.1.1: version "15.1.1" resolved "https://registry.npmmirror.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5"