diff --git a/apps/cli/src/commands/start_server/api_app/controllers/admin_get_user_list.ts b/apps/cli/src/commands/start_server/api_app/controllers/admin_get_user_list.ts index 618b88f4..e635f447 100644 --- a/apps/cli/src/commands/start_server/api_app/controllers/admin_get_user_list.ts +++ b/apps/cli/src/commands/start_server/api_app/controllers/admin_get_user_list.ts @@ -3,7 +3,7 @@ import { Response } from '#/server/api/admin_get_user_list'; import { getAssetPublicPath } from '@/platform/asset'; import { AssetType } from '#/constants'; import { USER_TABLE_NAME, User, UserProperty } from '@/constants/db_definition'; -import excludeProperty from '#/utils/exclude_property'; +import excludeProperties from '#/utils/exclude_properties'; import { UNUSED_2FA_SECRET_PREFIX } from '@/constants'; import { Context } from '../constants'; @@ -46,7 +46,7 @@ export default async (ctx: Context) => { ); return ctx.success( userList.map((user) => ({ - ...excludeProperty(user, [UserProperty.TWO_FA_SECRET]), + ...excludeProperties(user, [UserProperty.TWO_FA_SECRET]), avatar: getAssetPublicPath(user.avatar, AssetType.USER_AVATAR), twoFAEnabled: Boolean( user.twoFASecret && diff --git a/apps/cli/src/commands/start_server/api_app/controllers/get_exploration.ts b/apps/cli/src/commands/start_server/api_app/controllers/get_exploration.ts index e093dee2..fd9425fb 100644 --- a/apps/cli/src/commands/start_server/api_app/controllers/get_exploration.ts +++ b/apps/cli/src/commands/start_server/api_app/controllers/get_exploration.ts @@ -2,7 +2,7 @@ import { AssetType } from '#/constants'; import { Response } from '#/server/api/get_exploration'; import { getDB } from '@/db'; import { getSingerListInMusicIds } from '@/db/singer'; -import excludeProperty from '#/utils/exclude_property'; +import excludeProperties from '#/utils/exclude_properties'; import { Music, MusicProperty, @@ -116,14 +116,14 @@ export default async (ctx: Context) => { cover: getAssetPublicPath(m.cover, AssetType.MUSIC_COVER), singers: musicSingerList .filter((s) => s.musicId === m.id) - .map((s) => excludeProperty(s, ['musicId'])), + .map((s) => excludeProperties(s, ['musicId'])), })), singerList: singerList.map((s) => ({ ...s, avatar: getAssetPublicPath(s.avatar, AssetType.SINGER_AVATAR), })), publicMusicbillList: publicMusicbillList.map((mb) => ({ - ...excludeProperty(mb, [MusicbillProperty.USER_ID]), + ...excludeProperties(mb, [MusicbillProperty.USER_ID]), cover: getAssetPublicPath(mb.cover, AssetType.MUSICBILL_COVER), user: musicbillCreateUserList.find((u) => mb.userId === u.id)!, })), diff --git a/apps/cli/src/commands/start_server/api_app/controllers/get_music.ts b/apps/cli/src/commands/start_server/api_app/controllers/get_music.ts index d7602624..a7c72cfc 100644 --- a/apps/cli/src/commands/start_server/api_app/controllers/get_music.ts +++ b/apps/cli/src/commands/start_server/api_app/controllers/get_music.ts @@ -1,7 +1,7 @@ import { ALIAS_DIVIDER, AssetType } from '#/constants'; import { Response } from '#/server/api/get_music'; import { ExceptionCode } from '#/constants/exception'; -import excludeProperty from '#/utils/exclude_property'; +import excludeProperties from '#/utils/exclude_properties'; import { Music, MusicProperty, @@ -85,26 +85,23 @@ export default async (ctx: Context) => { ) : undefined, ]); - const musicIdMapSingerList: { - [key: string]: (Pick< + const musicIdMapSingerList: Record & { aliases: string[]; - })[]; - } = {}; + })[]> = {}; allSingerList.forEach((s) => { if (!musicIdMapSingerList[s.musicId]) { musicIdMapSingerList[s.musicId] = []; } musicIdMapSingerList[s.musicId].push({ - ...excludeProperty(s, ['musicId']), + ...excludeProperties(s, ['musicId']), aliases: s.aliases ? s.aliases.split(ALIAS_DIVIDER) : [], }); }); - const musicIdMapMusic: { - [key: string]: Pick< + const musicIdMapMusic: Record & { @@ -112,19 +109,18 @@ export default async (ctx: Context) => { Singer, SingerProperty.ID | SingerProperty.NAME | SingerProperty.AVATAR >[]; - }; - } = {}; + }> = {}; musicList.forEach((m) => { musicIdMapMusic[m.id] = { ...m, singers: (musicIdMapSingerList[m.id] || []).map((s) => - excludeProperty(s, ['aliases']), + excludeProperties(s, ['aliases']), ), }; }); return ctx.success({ - ...excludeProperty(music, [MusicProperty.CREATE_USER_ID]), + ...excludeProperties(music, [MusicProperty.CREATE_USER_ID]), cover: getAssetPublicPath(music.cover, AssetType.MUSIC_COVER), asset: getAssetPublicPath(music.asset, AssetType.MUSIC), aliases: music.aliases ? music.aliases.split(ALIAS_DIVIDER) : [], diff --git a/apps/cli/src/commands/start_server/api_app/controllers/get_music_list.ts b/apps/cli/src/commands/start_server/api_app/controllers/get_music_list.ts index 2daeb3b9..5e1aa4bc 100644 --- a/apps/cli/src/commands/start_server/api_app/controllers/get_music_list.ts +++ b/apps/cli/src/commands/start_server/api_app/controllers/get_music_list.ts @@ -1,7 +1,7 @@ import { ALIAS_DIVIDER, AssetType } from '#/constants'; import { ExceptionCode } from '#/constants/exception'; import { SEARCH_KEYWORD_MAX_LENGTH } from '#/constants/music'; -import excludeProperty from '#/utils/exclude_property'; +import excludeProperties from '#/utils/exclude_properties'; import { Music, MusicProperty, @@ -150,17 +150,18 @@ export default async (ctx: Context) => { musicList.map((m) => m.id), [SingerProperty.ID, SingerProperty.NAME, SingerProperty.ALIASES], ); - const musicIdMapSingerList: { - [key: string]: (Pick & { + const musicIdMapSingerList: Record< + string, + (Pick & { aliases: string[]; - })[]; - } = {}; + })[] + > = {}; singerList.forEach((s) => { if (!musicIdMapSingerList[s.musicId]) { musicIdMapSingerList[s.musicId] = []; } musicIdMapSingerList[s.musicId].push({ - ...excludeProperty(s, ['musicId']), + ...excludeProperties(s, ['musicId']), aliases: s.aliases ? s.aliases.split(ALIAS_DIVIDER) : [], }); }); diff --git a/apps/cli/src/commands/start_server/api_app/controllers/get_music_play_record_list.ts b/apps/cli/src/commands/start_server/api_app/controllers/get_music_play_record_list.ts index 55e21e73..dd0f8153 100644 --- a/apps/cli/src/commands/start_server/api_app/controllers/get_music_play_record_list.ts +++ b/apps/cli/src/commands/start_server/api_app/controllers/get_music_play_record_list.ts @@ -1,7 +1,7 @@ import { ALIAS_DIVIDER } from '#/constants'; import { ExceptionCode } from '#/constants/exception'; import { SEARCH_KEYWORD_MAX_LENGTH } from '#/constants/music'; -import excludeProperty from '#/utils/exclude_property'; +import excludeProperties from '#/utils/exclude_properties'; import { MUSIC_PLAY_RECORD_TABLE_NAME, MUSIC_SINGER_RELATION_TABLE_NAME, @@ -166,14 +166,12 @@ export default async (ctx: Context) => { musicPlayRecordList.map((m) => m.id), [SingerProperty.ID, SingerProperty.NAME], ); - const musicIdMapSingerList: { - [key: string]: Pick[]; - } = {}; + const musicIdMapSingerList: Record[]> = {}; singerList.forEach((s) => { if (!musicIdMapSingerList[s.musicId]) { musicIdMapSingerList[s.musicId] = []; } - musicIdMapSingerList[s.musicId].push(excludeProperty(s, ['musicId'])); + musicIdMapSingerList[s.musicId].push(excludeProperties(s, ['musicId'])); }); return ctx.success({ diff --git a/apps/cli/src/commands/start_server/api_app/controllers/get_musicbill.ts b/apps/cli/src/commands/start_server/api_app/controllers/get_musicbill.ts index ac62ac21..0ccc35d8 100644 --- a/apps/cli/src/commands/start_server/api_app/controllers/get_musicbill.ts +++ b/apps/cli/src/commands/start_server/api_app/controllers/get_musicbill.ts @@ -2,7 +2,7 @@ import { Response } from '#/server/api/get_musicbill'; import { ALIAS_DIVIDER, AssetType } from '#/constants'; import { ExceptionCode } from '#/constants/exception'; import { getSingerListInMusicIds } from '@/db/singer'; -import excludeProperty from '#/utils/exclude_property'; +import excludeProperties from '#/utils/exclude_properties'; import { getAssetPublicPath } from '@/platform/asset'; import { getDB } from '@/db'; import { @@ -130,22 +130,31 @@ export default async (ctx: Context) => { [id], ); - const musicIdMapSingers: Record = {}; + avatar: string; + }[] + > = {}; if (musicList.length) { const allSingerList = await getSingerListInMusicIds( Array.from(new Set(musicList.map((m) => m.id))), - [SingerProperty.ID, SingerProperty.NAME, SingerProperty.ALIASES], + [ + SingerProperty.ID, + SingerProperty.NAME, + SingerProperty.ALIASES, + SingerProperty.AVATAR, + ], ); for (const singer of allSingerList) { if (!musicIdMapSingers[singer.musicId]) { musicIdMapSingers[singer.musicId] = []; } musicIdMapSingers[singer.musicId].push({ - ...excludeProperty(singer, ['musicId']), + ...excludeProperties(singer, ['musicId']), aliases: singer.aliases ? singer.aliases.split(ALIAS_DIVIDER) : [], }); } @@ -171,7 +180,10 @@ export default async (ctx: Context) => { musicList: musicList.map((m) => ({ ...m, aliases: m.aliases ? m.aliases.split(ALIAS_DIVIDER) : [], - singers: musicIdMapSingers[m.id] || [], + singers: (musicIdMapSingers[m.id] || []).map((s) => ({ + ...s, + avatar: getAssetPublicPath(s.avatar, AssetType.SINGER_AVATAR), + })), cover: getAssetPublicPath(m.cover, AssetType.MUSIC_COVER), asset: getAssetPublicPath(m.asset, AssetType.MUSIC), })), diff --git a/apps/cli/src/commands/start_server/api_app/controllers/get_musicbill_list.ts b/apps/cli/src/commands/start_server/api_app/controllers/get_musicbill_list.ts index ac1d188d..e7ac5c5d 100644 --- a/apps/cli/src/commands/start_server/api_app/controllers/get_musicbill_list.ts +++ b/apps/cli/src/commands/start_server/api_app/controllers/get_musicbill_list.ts @@ -13,7 +13,7 @@ import { UserProperty, } from '@/constants/db_definition'; import { getDB } from '@/db'; -import excludeProperty from '#/utils/exclude_property'; +import excludeProperties from '#/utils/exclude_properties'; import { Context } from '../constants'; export default async (ctx: Context) => { @@ -101,7 +101,7 @@ export default async (ctx: Context) => { sharedUserList: sharedUserList .filter((u) => u.musicbillId === mb.id) .map((u) => ({ - ...excludeProperty(u, [SharedMusicbillProperty.MUSICBILL_ID]), + ...excludeProperties(u, [SharedMusicbillProperty.MUSICBILL_ID]), avatar: getAssetPublicPath(u.avatar, AssetType.USER_AVATAR), accepted: !!u.accepted, })), diff --git a/apps/cli/src/commands/start_server/api_app/controllers/get_public_musicbill.ts b/apps/cli/src/commands/start_server/api_app/controllers/get_public_musicbill.ts index 2e6f6984..46db41fc 100644 --- a/apps/cli/src/commands/start_server/api_app/controllers/get_public_musicbill.ts +++ b/apps/cli/src/commands/start_server/api_app/controllers/get_public_musicbill.ts @@ -1,7 +1,7 @@ import { Response } from '#/server/api/get_public_musicbill'; import { ALIAS_DIVIDER, AssetType } from '#/constants'; import { ExceptionCode } from '#/constants/exception'; -import excludeProperty from '#/utils/exclude_property'; +import excludeProperties from '#/utils/exclude_properties'; import { Music, MusicProperty, @@ -78,13 +78,11 @@ export default async (ctx: Context) => { }), ]); - const musicIdMapSingers: { - [key: string]: { + const musicIdMapSingers: Record = {}; if (musicList.length) { const allSingerList = await getSingerListInMusicIds( Array.from(new Set(musicList.map((m) => m.id))), @@ -95,14 +93,14 @@ export default async (ctx: Context) => { musicIdMapSingers[singer.musicId] = []; } musicIdMapSingers[singer.musicId].push({ - ...excludeProperty(singer, ['musicId']), + ...excludeProperties(singer, ['musicId']), aliases: singer.aliases ? singer.aliases.split(ALIAS_DIVIDER) : [], }); } } return ctx.success({ - ...excludeProperty(musicbill, [ + ...excludeProperties(musicbill, [ MusicbillProperty.PUBLIC, MusicbillProperty.USER_ID, ]), diff --git a/apps/cli/src/commands/start_server/api_app/controllers/get_public_musicbill_collection_list.ts b/apps/cli/src/commands/start_server/api_app/controllers/get_public_musicbill_collection_list.ts index 78214965..6ad66f01 100644 --- a/apps/cli/src/commands/start_server/api_app/controllers/get_public_musicbill_collection_list.ts +++ b/apps/cli/src/commands/start_server/api_app/controllers/get_public_musicbill_collection_list.ts @@ -1,7 +1,7 @@ import { ExceptionCode } from '#/constants/exception'; import { Response } from '#/server/api/get_public_musicbill_collection_list'; import { SEARCH_KEYWORD_MAX_LENGTH } from '#/constants/musicbill'; -import excludeProperty from '#/utils/exclude_property'; +import excludeProperties from '#/utils/exclude_properties'; import { PUBLIC_MUSICBILL_COLLECTION_TABLE_NAME, MUSICBILL_TABLE_NAME, @@ -139,7 +139,7 @@ export default async (ctx: Context) => { return ctx.success({ total, collectionList: collectionList.map((mb) => ({ - ...excludeProperty(mb, [MusicbillProperty.USER_ID]), + ...excludeProperties(mb, [MusicbillProperty.USER_ID]), user: userList.find((u) => u.id === mb.userId)!, cover: getAssetPublicPath(mb.cover, AssetType.MUSICBILL_COVER), })), diff --git a/apps/cli/src/commands/start_server/api_app/controllers/get_singer.ts b/apps/cli/src/commands/start_server/api_app/controllers/get_singer.ts index 96b3dab3..45b71092 100644 --- a/apps/cli/src/commands/start_server/api_app/controllers/get_singer.ts +++ b/apps/cli/src/commands/start_server/api_app/controllers/get_singer.ts @@ -1,7 +1,7 @@ import { ALIAS_DIVIDER, AssetType } from '#/constants'; import { ExceptionCode } from '#/constants/exception'; import { Response } from '#/server/api/get_singer'; -import excludeProperty from '#/utils/exclude_property'; +import excludeProperties from '#/utils/exclude_properties'; import { MUSIC_SINGER_RELATION_TABLE_NAME, MUSIC_TABLE_NAME, @@ -84,29 +84,38 @@ export default async (ctx: Context) => { ), ]); - const musicIdMapSingers: { - [key: string]: (Pick & { + const musicIdMapSingers: Record< + string, + (Pick< + Singer, + SingerProperty.ID | SingerProperty.NAME | SingerProperty.AVATAR + > & { aliases: string[]; - })[]; - } = {}; + })[] + > = {}; if (musicList.length) { const allSingerList = await getSingerListInMusicIds( Array.from(new Set(musicList.map((m) => m.id))), - [SingerProperty.ID, SingerProperty.NAME, SingerProperty.ALIASES], + [ + SingerProperty.ID, + SingerProperty.NAME, + SingerProperty.ALIASES, + SingerProperty.AVATAR, + ], ); allSingerList.forEach((s) => { if (!musicIdMapSingers[s.musicId]) { musicIdMapSingers[s.musicId] = []; } musicIdMapSingers[s.musicId].push({ - ...excludeProperty(s, ['musicId']), + ...excludeProperties(s, ['musicId']), aliases: s.aliases ? s.aliases.split(ALIAS_DIVIDER) : [], }); }); } return ctx.success({ - ...excludeProperty(singer, [SingerProperty.CREATE_USER_ID]), + ...excludeProperties(singer, [SingerProperty.CREATE_USER_ID]), avatar: getAssetPublicPath(singer.avatar, AssetType.SINGER_AVATAR), aliases: singer.aliases ? singer.aliases.split(ALIAS_DIVIDER) : [], createUser: createUser!, @@ -115,7 +124,10 @@ export default async (ctx: Context) => { cover: getAssetPublicPath(m.cover, AssetType.MUSIC_COVER), asset: getAssetPublicPath(m.asset, AssetType.MUSIC), aliases: m.aliases ? m.aliases.split(ALIAS_DIVIDER) : [], - singers: musicIdMapSingers[m.id] || [], + singers: (musicIdMapSingers[m.id] || []).map((s) => ({ + ...s, + avatar: getAssetPublicPath(s.avatar, AssetType.SINGER_AVATAR), + })), })), editable: !!editable, diff --git a/apps/cli/src/commands/start_server/api_app/controllers/get_user.ts b/apps/cli/src/commands/start_server/api_app/controllers/get_user.ts index 8d090fbf..950c051a 100644 --- a/apps/cli/src/commands/start_server/api_app/controllers/get_user.ts +++ b/apps/cli/src/commands/start_server/api_app/controllers/get_user.ts @@ -4,7 +4,7 @@ import { ExceptionCode } from '#/constants/exception'; import { getUserById } from '@/db/user'; import { getDB } from '@/db'; import { getSingerListInMusicIds } from '@/db/singer'; -import excludeProperty from '#/utils/exclude_property'; +import excludeProperties from '#/utils/exclude_properties'; import { Music, MusicProperty, @@ -90,24 +90,31 @@ export default async (ctx: Context) => { ), ]); - const musicIdMapSingers: { - [key: string]: { + const musicIdMapSingers: Record< + string, + { id: string; name: string; aliases: string[]; - }[]; - } = {}; + avatar: string; + }[] + > = {}; if (musicList.length) { const allSingerList = await getSingerListInMusicIds( Array.from(new Set(musicList.map((m) => m.id))), - [SingerProperty.ID, SingerProperty.NAME, SingerProperty.ALIASES], + [ + SingerProperty.ID, + SingerProperty.NAME, + SingerProperty.ALIASES, + SingerProperty.AVATAR, + ], ); for (const singer of allSingerList) { if (!musicIdMapSingers[singer.musicId]) { musicIdMapSingers[singer.musicId] = []; } musicIdMapSingers[singer.musicId].push({ - ...excludeProperty(singer, ['musicId']), + ...excludeProperties(singer, ['musicId']), aliases: singer.aliases ? singer.aliases.split(ALIAS_DIVIDER) : [], }); } @@ -125,7 +132,10 @@ export default async (ctx: Context) => { cover: getAssetPublicPath(m.cover, AssetType.MUSIC_COVER), asset: getAssetPublicPath(m.asset, AssetType.MUSIC), aliases: m.aliases ? m.aliases.split(ALIAS_DIVIDER) : [], - singers: musicIdMapSingers[m.id] || [], + singers: (musicIdMapSingers[m.id] || []).map((s) => ({ + ...s, + avatar: getAssetPublicPath(s.avatar, AssetType.SINGER_AVATAR), + })), })), }); }; diff --git a/apps/cli/src/commands/start_server/api_app/controllers/search_music.ts b/apps/cli/src/commands/start_server/api_app/controllers/search_music.ts index 076d06da..3fb60582 100644 --- a/apps/cli/src/commands/start_server/api_app/controllers/search_music.ts +++ b/apps/cli/src/commands/start_server/api_app/controllers/search_music.ts @@ -4,7 +4,7 @@ import { ExceptionCode } from '#/constants/exception'; import { SEARCH_KEYWORD_MAX_LENGTH } from '#/constants/music'; import { getDB } from '@/db'; import { getSingerListInMusicIds } from '@/db/singer'; -import excludeProperty from '#/utils/exclude_property'; +import excludeProperties from '#/utils/exclude_properties'; import { Music, MusicProperty, @@ -139,17 +139,15 @@ export default async (ctx: Context) => { musicList.map((m) => m.id), [SingerProperty.ID, SingerProperty.NAME, SingerProperty.ALIASES], ); - const musicIdMapSingerList: { - [key: string]: (Pick & { + const musicIdMapSingerList: Record & { aliases: string[]; - })[]; - } = {}; + })[]> = {}; singerList.forEach((s) => { if (!musicIdMapSingerList[s.musicId]) { musicIdMapSingerList[s.musicId] = []; } musicIdMapSingerList[s.musicId].push({ - ...excludeProperty(s, ['musicId']), + ...excludeProperties(s, ['musicId']), aliases: s.aliases ? s.aliases.split(ALIAS_DIVIDER) : [], }); }); @@ -157,7 +155,7 @@ export default async (ctx: Context) => { return ctx.success({ total, musicList: musicList.map((m) => ({ - ...excludeProperty(m, [MusicProperty.CREATE_USER_ID]), + ...excludeProperties(m, [MusicProperty.CREATE_USER_ID]), cover: getAssetPublicPath(m.cover, AssetType.MUSIC_COVER), asset: getAssetPublicPath(m.asset, AssetType.MUSIC), aliases: m.aliases ? m.aliases.split(ALIAS_DIVIDER) : [], diff --git a/apps/cli/src/commands/start_server/api_app/controllers/search_music_by_lyric.ts b/apps/cli/src/commands/start_server/api_app/controllers/search_music_by_lyric.ts index c8e74210..8f85f024 100644 --- a/apps/cli/src/commands/start_server/api_app/controllers/search_music_by_lyric.ts +++ b/apps/cli/src/commands/start_server/api_app/controllers/search_music_by_lyric.ts @@ -2,7 +2,7 @@ import { Response } from '#/server/api/search_music_by_lyric'; import { ALIAS_DIVIDER, AssetType } from '#/constants'; import { ExceptionCode } from '#/constants/exception'; import { SEARCH_KEYWORD_MAX_LENGTH } from '#/constants/music'; -import excludeProperty from '#/utils/exclude_property'; +import excludeProperties from '#/utils/exclude_properties'; import { LYRIC_TABLE_NAME, Lyric, @@ -112,29 +112,25 @@ export default async (ctx: Context) => { ), ]); - const musicIdMapLyricList: { - [key: string]: Pick[]; - } = {}; + const musicIdMapLyricList: Record[]> = {}; lyricList.forEach((lyric) => { if (!musicIdMapLyricList[lyric.musicId]) { musicIdMapLyricList[lyric.musicId] = []; } musicIdMapLyricList[lyric.musicId].push( - excludeProperty(lyric, [LyricProperty.MUSIC_ID]), + excludeProperties(lyric, [LyricProperty.MUSIC_ID]), ); }); - const musicIdMapSingerList: { - [key: string]: (Pick & { + const musicIdMapSingerList: Record & { aliases: string[]; - })[]; - } = {}; + })[]> = {}; singerList.forEach((singer) => { if (!musicIdMapSingerList[singer.musicId]) { musicIdMapSingerList[singer.musicId] = []; } musicIdMapSingerList[singer.musicId].push({ - ...excludeProperty(singer, ['musicId']), + ...excludeProperties(singer, ['musicId']), aliases: singer.aliases ? singer.aliases.split(ALIAS_DIVIDER) : [], }); }); diff --git a/apps/cli/src/commands/start_server/api_app/controllers/search_public_musicbill.ts b/apps/cli/src/commands/start_server/api_app/controllers/search_public_musicbill.ts index a44f0206..86e79e8d 100644 --- a/apps/cli/src/commands/start_server/api_app/controllers/search_public_musicbill.ts +++ b/apps/cli/src/commands/start_server/api_app/controllers/search_public_musicbill.ts @@ -1,7 +1,7 @@ import { ExceptionCode } from '#/constants/exception'; import { Response } from '#/server/api/search_public_musicbill'; import { SEARCH_KEYWORD_MAX_LENGTH } from '#/constants/musicbill'; -import excludeProperty from '#/utils/exclude_property'; +import excludeProperties from '#/utils/exclude_properties'; import { PUBLIC_MUSICBILL_COLLECTION_TABLE_NAME, MUSICBILL_MUSIC_TABLE_NAME, @@ -155,7 +155,7 @@ export default async (ctx: Context) => { musicbillList: musicbillList.map((mb) => { const user = userList.find((u) => u.id === mb.userId)!; return { - ...excludeProperty(mb, [MusicbillProperty.USER_ID]), + ...excludeProperties(mb, [MusicbillProperty.USER_ID]), cover: getAssetPublicPath(mb.cover, AssetType.MUSICBILL_COVER), user, }; diff --git a/apps/flutter/.gitignore b/apps/flutter/.gitignore new file mode 100644 index 00000000..79c113f9 --- /dev/null +++ b/apps/flutter/.gitignore @@ -0,0 +1,45 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/apps/flutter/.metadata b/apps/flutter/.metadata new file mode 100644 index 00000000..fdb4416b --- /dev/null +++ b/apps/flutter/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "fcf2c11572af6f390246c056bc905eca609533a0" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: fcf2c11572af6f390246c056bc905eca609533a0 + base_revision: fcf2c11572af6f390246c056bc905eca609533a0 + - platform: android + create_revision: fcf2c11572af6f390246c056bc905eca609533a0 + base_revision: fcf2c11572af6f390246c056bc905eca609533a0 + - platform: ios + create_revision: fcf2c11572af6f390246c056bc905eca609533a0 + base_revision: fcf2c11572af6f390246c056bc905eca609533a0 + - platform: linux + create_revision: fcf2c11572af6f390246c056bc905eca609533a0 + base_revision: fcf2c11572af6f390246c056bc905eca609533a0 + - platform: macos + create_revision: fcf2c11572af6f390246c056bc905eca609533a0 + base_revision: fcf2c11572af6f390246c056bc905eca609533a0 + - platform: web + create_revision: fcf2c11572af6f390246c056bc905eca609533a0 + base_revision: fcf2c11572af6f390246c056bc905eca609533a0 + - platform: windows + create_revision: fcf2c11572af6f390246c056bc905eca609533a0 + base_revision: fcf2c11572af6f390246c056bc905eca609533a0 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/apps/flutter/README.md b/apps/flutter/README.md new file mode 100644 index 00000000..0ec233a8 --- /dev/null +++ b/apps/flutter/README.md @@ -0,0 +1,3 @@ +# Cicada + +Client for [cicada](https://github.com/mebtte/cicada), powered by [flutter](https://flutter.dev). diff --git a/apps/flutter/analysis_options.yaml b/apps/flutter/analysis_options.yaml new file mode 100644 index 00000000..31674fe9 --- /dev/null +++ b/apps/flutter/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml +analyzer: + errors: + constant_identifier_names: ignore diff --git a/apps/flutter/android/.gitignore b/apps/flutter/android/.gitignore new file mode 100644 index 00000000..be3943c9 --- /dev/null +++ b/apps/flutter/android/.gitignore @@ -0,0 +1,14 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java +.cxx/ + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/apps/flutter/android/app/build.gradle.kts b/apps/flutter/android/app/build.gradle.kts new file mode 100644 index 00000000..784dc892 --- /dev/null +++ b/apps/flutter/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.mebtte.cicada" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.mebtte.cicada" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/apps/flutter/android/app/src/debug/AndroidManifest.xml b/apps/flutter/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 00000000..399f6981 --- /dev/null +++ b/apps/flutter/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/apps/flutter/android/app/src/main/AndroidManifest.xml b/apps/flutter/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..34c3ca47 --- /dev/null +++ b/apps/flutter/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/flutter/android/app/src/main/kotlin/com/example/cicada/MainActivity.kt b/apps/flutter/android/app/src/main/kotlin/com/example/cicada/MainActivity.kt new file mode 100644 index 00000000..58f82787 --- /dev/null +++ b/apps/flutter/android/app/src/main/kotlin/com/example/cicada/MainActivity.kt @@ -0,0 +1,5 @@ +package com.mebtte.cicada + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/apps/flutter/android/app/src/main/res/drawable-v21/launch_background.xml b/apps/flutter/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 00000000..f74085f3 --- /dev/null +++ b/apps/flutter/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/apps/flutter/android/app/src/main/res/drawable/launch_background.xml b/apps/flutter/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 00000000..304732f8 --- /dev/null +++ b/apps/flutter/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/apps/flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/apps/flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..db77bb4b Binary files /dev/null and b/apps/flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/apps/flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/apps/flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..17987b79 Binary files /dev/null and b/apps/flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/apps/flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/apps/flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..09d43914 Binary files /dev/null and b/apps/flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/apps/flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/apps/flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..d5f1c8d3 Binary files /dev/null and b/apps/flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/apps/flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/apps/flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..4d6372ee Binary files /dev/null and b/apps/flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/apps/flutter/android/app/src/main/res/values-night/styles.xml b/apps/flutter/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 00000000..06952be7 --- /dev/null +++ b/apps/flutter/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/apps/flutter/android/app/src/main/res/values/styles.xml b/apps/flutter/android/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..cb1ef880 --- /dev/null +++ b/apps/flutter/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/apps/flutter/android/app/src/profile/AndroidManifest.xml b/apps/flutter/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 00000000..399f6981 --- /dev/null +++ b/apps/flutter/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/apps/flutter/android/build.gradle.kts b/apps/flutter/android/build.gradle.kts new file mode 100644 index 00000000..89176ef4 --- /dev/null +++ b/apps/flutter/android/build.gradle.kts @@ -0,0 +1,21 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/apps/flutter/android/gradle.properties b/apps/flutter/android/gradle.properties new file mode 100644 index 00000000..f018a618 --- /dev/null +++ b/apps/flutter/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/apps/flutter/android/gradle/wrapper/gradle-wrapper.properties b/apps/flutter/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..ac3b4792 --- /dev/null +++ b/apps/flutter/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip diff --git a/apps/flutter/android/settings.gradle.kts b/apps/flutter/android/settings.gradle.kts new file mode 100644 index 00000000..ab39a10a --- /dev/null +++ b/apps/flutter/android/settings.gradle.kts @@ -0,0 +1,25 @@ +pluginManagement { + val flutterSdkPath = run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.7.3" apply false + id("org.jetbrains.kotlin.android") version "2.1.0" apply false +} + +include(":app") diff --git a/apps/flutter/devtools_options.yaml b/apps/flutter/devtools_options.yaml new file mode 100644 index 00000000..4dcfde9f --- /dev/null +++ b/apps/flutter/devtools_options.yaml @@ -0,0 +1,4 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: + - shared_preferences: true \ No newline at end of file diff --git a/apps/flutter/ios/.gitignore b/apps/flutter/ios/.gitignore new file mode 100644 index 00000000..7a7f9873 --- /dev/null +++ b/apps/flutter/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/apps/flutter/ios/Flutter/AppFrameworkInfo.plist b/apps/flutter/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 00000000..1dc6cf76 --- /dev/null +++ b/apps/flutter/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 13.0 + + diff --git a/apps/flutter/ios/Flutter/Debug.xcconfig b/apps/flutter/ios/Flutter/Debug.xcconfig new file mode 100644 index 00000000..ec97fc6f --- /dev/null +++ b/apps/flutter/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/apps/flutter/ios/Flutter/Release.xcconfig b/apps/flutter/ios/Flutter/Release.xcconfig new file mode 100644 index 00000000..c4855bfe --- /dev/null +++ b/apps/flutter/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/apps/flutter/ios/Podfile b/apps/flutter/ios/Podfile new file mode 100644 index 00000000..620e46eb --- /dev/null +++ b/apps/flutter/ios/Podfile @@ -0,0 +1,43 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '13.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/apps/flutter/ios/Podfile.lock b/apps/flutter/ios/Podfile.lock new file mode 100644 index 00000000..6760d616 --- /dev/null +++ b/apps/flutter/ios/Podfile.lock @@ -0,0 +1,57 @@ +PODS: + - audio_service (0.0.1): + - Flutter + - FlutterMacOS + - audio_session (0.0.1): + - Flutter + - Flutter (1.0.0) + - just_audio (0.0.1): + - Flutter + - FlutterMacOS + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS + - sqflite_darwin (0.0.4): + - Flutter + - FlutterMacOS + +DEPENDENCIES: + - audio_service (from `.symlinks/plugins/audio_service/darwin`) + - audio_session (from `.symlinks/plugins/audio_session/ios`) + - Flutter (from `Flutter`) + - just_audio (from `.symlinks/plugins/just_audio/darwin`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) + - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) + - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) + +EXTERNAL SOURCES: + audio_service: + :path: ".symlinks/plugins/audio_service/darwin" + audio_session: + :path: ".symlinks/plugins/audio_session/ios" + Flutter: + :path: Flutter + just_audio: + :path: ".symlinks/plugins/just_audio/darwin" + path_provider_foundation: + :path: ".symlinks/plugins/path_provider_foundation/darwin" + shared_preferences_foundation: + :path: ".symlinks/plugins/shared_preferences_foundation/darwin" + sqflite_darwin: + :path: ".symlinks/plugins/sqflite_darwin/darwin" + +SPEC CHECKSUMS: + audio_service: aa99a6ba2ae7565996015322b0bb024e1d25c6fd + audio_session: 9bb7f6c970f21241b19f5a3658097ae459681ba0 + Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 + just_audio: 4e391f57b79cad2b0674030a00453ca5ce817eed + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 + sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 + +PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e + +COCOAPODS: 1.16.2 diff --git a/apps/flutter/ios/Runner.xcodeproj/project.pbxproj b/apps/flutter/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..26ed750c --- /dev/null +++ b/apps/flutter/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,731 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 044B51E3660D3CDC4DE3B87A /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 888536450B4A0C687FF2DDFF /* Pods_RunnerTests.framework */; }; + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 971DFEA4F8E5C12BA3F58F7D /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D9B4B0C6968A47C4B6753EB6 /* Pods_Runner.framework */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 15025F906650C87EF40CA75C /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 47658916E445717C8CB9E93A /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 820F2ECDC21C4EC47BED19F2 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 888536450B4A0C687FF2DDFF /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9C580346454A47CA3C0081AA /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + B7EAA5FF93D5FC3D66B04747 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + C0DF4634F61DEAEF4541550E /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + D9B4B0C6968A47C4B6753EB6 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 0EA138972B77EE585A0F915F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 044B51E3660D3CDC4DE3B87A /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 971DFEA4F8E5C12BA3F58F7D /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 47DE4B7A8AFC6BAD184043CA /* Frameworks */ = { + isa = PBXGroup; + children = ( + D9B4B0C6968A47C4B6753EB6 /* Pods_Runner.framework */, + 888536450B4A0C687FF2DDFF /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + C53CBE028FEC48F193722DAE /* Pods */, + 47DE4B7A8AFC6BAD184043CA /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + C53CBE028FEC48F193722DAE /* Pods */ = { + isa = PBXGroup; + children = ( + 15025F906650C87EF40CA75C /* Pods-Runner.debug.xcconfig */, + 820F2ECDC21C4EC47BED19F2 /* Pods-Runner.release.xcconfig */, + B7EAA5FF93D5FC3D66B04747 /* Pods-Runner.profile.xcconfig */, + 9C580346454A47CA3C0081AA /* Pods-RunnerTests.debug.xcconfig */, + C0DF4634F61DEAEF4541550E /* Pods-RunnerTests.release.xcconfig */, + 47658916E445717C8CB9E93A /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 1127D9782503D8DDEC49ED03 /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + 0EA138972B77EE585A0F915F /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 2BAABC80DEECA05D10C1836C /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + AA1627D9F4559A7406269F89 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 1127D9782503D8DDEC49ED03 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 2BAABC80DEECA05D10C1836C /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + AA1627D9F4559A7406269F89 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = GCDGT9G53T; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.mebtte.cicada; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9C580346454A47CA3C0081AA /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.mebtte.cicada.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C0DF4634F61DEAEF4541550E /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.mebtte.cicada.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 47658916E445717C8CB9E93A /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.mebtte.cicada.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = GCDGT9G53T; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.mebtte.cicada; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = GCDGT9G53T; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.mebtte.cicada; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/apps/flutter/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/apps/flutter/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/apps/flutter/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/apps/flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/apps/flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/apps/flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/apps/flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/apps/flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/apps/flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/apps/flutter/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/apps/flutter/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..e3773d42 --- /dev/null +++ b/apps/flutter/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/flutter/ios/Runner.xcworkspace/contents.xcworkspacedata b/apps/flutter/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..21a3cc14 --- /dev/null +++ b/apps/flutter/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/apps/flutter/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/apps/flutter/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/apps/flutter/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/apps/flutter/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/apps/flutter/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/apps/flutter/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/apps/flutter/ios/Runner/AppDelegate.swift b/apps/flutter/ios/Runner/AppDelegate.swift new file mode 100644 index 00000000..62666446 --- /dev/null +++ b/apps/flutter/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..e882ab98 --- /dev/null +++ b/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images": [ + { + "size": "20x20", + "idiom": "iphone", + "filename": "Icon-App-20x20@2x.png", + "scale": "2x" + }, + { + "size": "20x20", + "idiom": "iphone", + "filename": "Icon-App-20x20@3x.png", + "scale": "3x" + }, + { + "size": "29x29", + "idiom": "iphone", + "filename": "Icon-App-29x29@1x.png", + "scale": "1x" + }, + { + "size": "29x29", + "idiom": "iphone", + "filename": "Icon-App-29x29@2x.png", + "scale": "2x" + }, + { + "size": "29x29", + "idiom": "iphone", + "filename": "Icon-App-29x29@3x.png", + "scale": "3x" + }, + { + "size": "40x40", + "idiom": "iphone", + "filename": "Icon-App-40x40@2x.png", + "scale": "2x" + }, + { + "size": "40x40", + "idiom": "iphone", + "filename": "Icon-App-40x40@3x.png", + "scale": "3x" + }, + { + "size": "60x60", + "idiom": "iphone", + "filename": "Icon-App-60x60@2x.png", + "scale": "2x" + }, + { + "size": "60x60", + "idiom": "iphone", + "filename": "Icon-App-60x60@3x.png", + "scale": "3x" + }, + { + "size": "20x20", + "idiom": "ipad", + "filename": "Icon-App-20x20@1x.png", + "scale": "1x" + }, + { + "size": "20x20", + "idiom": "ipad", + "filename": "Icon-App-20x20@2x.png", + "scale": "2x" + }, + { + "size": "29x29", + "idiom": "ipad", + "filename": "Icon-App-29x29@1x.png", + "scale": "1x" + }, + { + "size": "29x29", + "idiom": "ipad", + "filename": "Icon-App-29x29@2x.png", + "scale": "2x" + }, + { + "size": "40x40", + "idiom": "ipad", + "filename": "Icon-App-40x40@1x.png", + "scale": "1x" + }, + { + "size": "40x40", + "idiom": "ipad", + "filename": "Icon-App-40x40@2x.png", + "scale": "2x" + }, + { + "size": "76x76", + "idiom": "ipad", + "filename": "Icon-App-76x76@1x.png", + "scale": "1x" + }, + { + "size": "76x76", + "idiom": "ipad", + "filename": "Icon-App-76x76@2x.png", + "scale": "2x" + }, + { + "size": "83.5x83.5", + "idiom": "ipad", + "filename": "Icon-App-83.5x83.5@2x.png", + "scale": "2x" + }, + { + "size": "1024x1024", + "idiom": "ios-marketing", + "filename": "Icon-App-1024x1024@1x.png", + "scale": "1x" + } + ], + "info": { + "version": 1, + "author": "xcode" + } +} diff --git a/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 00000000..dc9ada47 Binary files /dev/null and b/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 00000000..7353c41e Binary files /dev/null and b/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 00000000..797d452e Binary files /dev/null and b/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 00000000..6ed2d933 Binary files /dev/null and b/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 00000000..4cd7b009 Binary files /dev/null and b/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 00000000..fe730945 Binary files /dev/null and b/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 00000000..321773cd Binary files /dev/null and b/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 00000000..797d452e Binary files /dev/null and b/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 00000000..502f463a Binary files /dev/null and b/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 00000000..0ec30343 Binary files /dev/null and b/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 00000000..0ec30343 Binary files /dev/null and b/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 00000000..e9f5fea2 Binary files /dev/null and b/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 00000000..84ac32ae Binary files /dev/null and b/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 00000000..8953cba0 Binary files /dev/null and b/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 00000000..0467bf12 Binary files /dev/null and b/apps/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/apps/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/apps/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 00000000..781d7cdc --- /dev/null +++ b/apps/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images": [ + { + "idiom": "universal", + "filename": "LaunchImage.png", + "scale": "1x" + }, + { + "idiom": "universal", + "filename": "LaunchImage@2x.png", + "scale": "2x" + }, + { + "idiom": "universal", + "filename": "LaunchImage@3x.png", + "scale": "3x" + } + ], + "info": { + "version": 1, + "author": "xcode" + } +} diff --git a/apps/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/apps/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/apps/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/apps/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/apps/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/apps/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/apps/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/apps/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/apps/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/apps/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/apps/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 00000000..b5b843ad --- /dev/null +++ b/apps/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. diff --git a/apps/flutter/ios/Runner/Base.lproj/LaunchScreen.storyboard b/apps/flutter/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..f2e259c7 --- /dev/null +++ b/apps/flutter/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/flutter/ios/Runner/Base.lproj/Main.storyboard b/apps/flutter/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 00000000..f3c28516 --- /dev/null +++ b/apps/flutter/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/flutter/ios/Runner/Info.plist b/apps/flutter/ios/Runner/Info.plist new file mode 100644 index 00000000..8c9f5554 --- /dev/null +++ b/apps/flutter/ios/Runner/Info.plist @@ -0,0 +1,53 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Cicada + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Cicada + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + UIBackgroundModes + + audio + + + diff --git a/apps/flutter/ios/Runner/Runner-Bridging-Header.h b/apps/flutter/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 00000000..308a2a56 --- /dev/null +++ b/apps/flutter/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/apps/flutter/ios/RunnerTests/RunnerTests.swift b/apps/flutter/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..86a7c3b1 --- /dev/null +++ b/apps/flutter/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/apps/flutter/lib/app.dart b/apps/flutter/lib/app.dart new file mode 100644 index 00000000..3461f242 --- /dev/null +++ b/apps/flutter/lib/app.dart @@ -0,0 +1,89 @@ +import 'package:cicada/audio_handler.dart'; +import 'package:cicada/play_indicator/index.dart'; +import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; +import 'package:provider/provider.dart'; +import './states/playlist.dart'; +import './states/playqueue.dart'; +import './pages/home/index.dart'; +import './server_management/index.dart'; +import './states/musicbill.dart' as musicbill_state; +import './user_management/index.dart'; +import './states/server.dart'; +import './pages/musicbill/index.dart' as musicbill_page; + +class AppContent extends StatefulWidget { + const AppContent({super.key}); + + @override + State createState() => _AppContentState(); +} + +class _AppContentState extends State { + @override + void initState() { + super.initState(); + musicbill_state.musicbillState.reloadMusicbillList(silence: false); + } + + @override + void reassemble() { + super.reassemble(); + GetIt.instance.get().stop(); + } + + @override + Widget build(BuildContext context) { + return MultiProvider( + providers: [ + ChangeNotifierProvider.value(value: musicbill_state.musicbillState), + ChangeNotifierProvider.value(value: playlistState), + ChangeNotifierProvider.value(value: playqueueState), + ], + child: MaterialApp( + home: Column( + children: [ + Expanded( + child: Navigator( + key: GlobalKey(), + onGenerateRoute: (setting) { + switch (setting.name) { + case '/musicbill': + { + final args = setting.arguments as Map; + return MaterialPageRoute( + builder: (_) => + musicbill_page.Musicbill(id: args['id']), + ); + } + default: + { + return MaterialPageRoute(builder: (_) => Home()); + } + } + }, + ), + ), + PlayIndicatorContainer(), + ], + ), + ), + ); + } +} + +class App extends StatelessWidget { + const App({super.key}); + + @override + Widget build(BuildContext context) { + final serverState = context.watch(); + return MaterialApp( + home: serverState.currentServer == null + ? ServerManagement() + : serverState.currentUser == null + ? UserManagement() + : AppContent(), + ); + } +} diff --git a/apps/flutter/lib/audio_handler.dart b/apps/flutter/lib/audio_handler.dart new file mode 100644 index 00000000..81a40f83 --- /dev/null +++ b/apps/flutter/lib/audio_handler.dart @@ -0,0 +1,93 @@ +import 'package:audio_service/audio_service.dart'; +import 'package:just_audio/just_audio.dart'; +import './states/playqueue.dart'; + +class MyAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler { + PlayqueueMusic? lastQueueMusic; + final player = AudioPlayer(); + + MyAudioHandler() { + player.playerStateStream.listen(_broadcastState); + } + + @override + Future play() => player.play(); + + @override + Future pause() => player.pause(); + + @override + Future skipToPrevious() async => playqueueState.previous(); + + @override + Future skipToNext() async => playqueueState.next(); + + @override + Future seek(Duration position) => player.seek(position); + + Future playQueueMusic(PlayqueueMusic queueMusic) async { + var duration = await player.setAudioSource( + AudioSource.uri(Uri.parse(queueMusic.music.asset)), + ); + player.play(); + + var item = MediaItem( + id: queueMusic.pid, + title: queueMusic.music.name, + artist: queueMusic.music.singers.map((s) => s.name).join(','), + artUri: queueMusic.music.cover == null + ? null + : Uri.parse(queueMusic.music.cover!), + duration: duration, + ); + mediaItem.add(item); + } + + void listen() { + playqueueState.addListener(() { + final currentQueueMusic = playqueueState.currentMusic; + if (currentQueueMusic != null && + currentQueueMusic.pid != lastQueueMusic?.pid) { + lastQueueMusic = currentQueueMusic; + playQueueMusic(currentQueueMusic); + } + }); + } + + void _broadcastState(PlayerState state) { + playbackState.add( + PlaybackState( + controls: [ + if (playqueueState.playqueueIndex > 0) MediaControl.skipToPrevious, + state.playing ? MediaControl.pause : MediaControl.play, + MediaControl.skipToNext, + ], + systemActions: { + MediaAction.play, + MediaAction.pause, + MediaAction.playPause, + MediaAction.seek, + MediaAction.seekForward, + MediaAction.seekBackward, + MediaAction.skipToPrevious, + MediaAction.skipToNext, + }, + processingState: { + ProcessingState.idle: AudioProcessingState.idle, + ProcessingState.loading: AudioProcessingState.loading, + ProcessingState.buffering: AudioProcessingState.buffering, + ProcessingState.ready: AudioProcessingState.ready, + ProcessingState.completed: AudioProcessingState.completed, + }[state.processingState]!, + playing: state.playing, + updatePosition: player.position, + bufferedPosition: player.bufferedPosition, + speed: player.speed, + ), + ); + + if (state.processingState == ProcessingState.completed) { + playqueueState.next(); + } + } +} diff --git a/apps/flutter/lib/constants/index.dart b/apps/flutter/lib/constants/index.dart new file mode 100644 index 00000000..4b0f4e1d --- /dev/null +++ b/apps/flutter/lib/constants/index.dart @@ -0,0 +1 @@ +final TOKEN_HEADER_KEY = "x-cicada-token"; diff --git a/apps/flutter/lib/event_bus.dart b/apps/flutter/lib/event_bus.dart new file mode 100644 index 00000000..4a7811f1 --- /dev/null +++ b/apps/flutter/lib/event_bus.dart @@ -0,0 +1,14 @@ +import 'package:cicada/models/music.dart'; +import 'package:event_bus/event_bus.dart'; + +class PlayMusicEvent { + Music music; + PlayMusicEvent({required this.music}); +} + +class AddMusicListToPlaylistEvent { + List musicList; + AddMusicListToPlaylistEvent({required this.musicList}); +} + +EventBus eventBus = EventBus(); diff --git a/apps/flutter/lib/extensions/list.dart b/apps/flutter/lib/extensions/list.dart new file mode 100644 index 00000000..5149bd14 --- /dev/null +++ b/apps/flutter/lib/extensions/list.dart @@ -0,0 +1,16 @@ +extension FirstWhereOrNull on List { + T? firstWhereOrNull(bool Function(T element) test) { + for (T element in this) { + if (test(element)) { + return element; + } + } + return null; + } +} + +extension SafeGet on List { + T? safeGet(int index) { + return index < 0 || index >= length ? null : this[index]; + } +} diff --git a/apps/flutter/lib/main.dart b/apps/flutter/lib/main.dart new file mode 100644 index 00000000..f62ce7aa --- /dev/null +++ b/apps/flutter/lib/main.dart @@ -0,0 +1,43 @@ +import 'package:cicada/states/playqueue.dart'; +import 'package:flutter/foundation.dart' + show kIsWeb, defaultTargetPlatform, TargetPlatform; +import 'package:audio_service/audio_service.dart'; +import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; +import 'package:provider/provider.dart'; +import './states/playlist.dart'; +import './utils/preference.dart'; +import './window_manager.dart'; +import './app.dart'; +import './states/server.dart'; +import './audio_handler.dart'; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await preference.initialize(); + + if (!kIsWeb && + [ + TargetPlatform.linux, + TargetPlatform.macOS, + TargetPlatform.windows, + ].contains(defaultTargetPlatform)) { + initializeWindow(); + } + + final audioHandler = await AudioService.init(builder: () => MyAudioHandler()); + audioHandler.listen(); + GetIt.instance.registerSingleton(audioHandler); + + await serverState.initialize(); + + playlistState.listen(); + playqueueState.listen(); + + runApp( + MultiProvider( + providers: [ChangeNotifierProvider.value(value: serverState)], + child: App(), + ), + ); +} diff --git a/apps/flutter/lib/models/music.dart b/apps/flutter/lib/models/music.dart new file mode 100644 index 00000000..def9c31d --- /dev/null +++ b/apps/flutter/lib/models/music.dart @@ -0,0 +1,28 @@ +import 'package:cicada/models/singer.dart'; +import 'package:cicada/utils/prefix_server_origin.dart'; + +class Music { + final String id; + final String name; + final String asset; + final String? cover; + final List singers; + + Music({ + required this.id, + required this.name, + required this.asset, + required this.cover, + required this.singers, + }); + + factory Music.fromJSON(Map json) => Music( + id: json['id'], + name: json['name'], + asset: prefixServerOrigin(json['asset'])!, + cover: prefixServerOrigin(json['cover']), + singers: (json['singers'] as List) + .map((json) => Singer.fromJSON(json)) + .toList(), + ); +} diff --git a/apps/flutter/lib/models/singer.dart b/apps/flutter/lib/models/singer.dart new file mode 100644 index 00000000..23d98bfe --- /dev/null +++ b/apps/flutter/lib/models/singer.dart @@ -0,0 +1,22 @@ +import 'package:cicada/utils/prefix_server_origin.dart'; + +class Singer { + final String id; + final String name; + final String? avatar; + final List aliases; + + Singer({ + required this.id, + required this.name, + required this.avatar, + required this.aliases, + }); + + factory Singer.fromJSON(Map json) => Singer( + id: json['id'], + name: json['name'], + avatar: prefixServerOrigin(json['avatar']), + aliases: List.from(json['aliases']), + ); +} diff --git a/apps/flutter/lib/pages/home/index.dart b/apps/flutter/lib/pages/home/index.dart new file mode 100644 index 00000000..d391c3de --- /dev/null +++ b/apps/flutter/lib/pages/home/index.dart @@ -0,0 +1,37 @@ +import '../../states/musicbill.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class Home extends StatelessWidget { + const Home({super.key}); + + @override + Widget build(BuildContext context) { + final musicbillList = context.watch().musicbillList; + return Scaffold( + appBar: AppBar(title: Text("My Musicbill")), + body: Column( + children: [ + if (musicbillList.isNotEmpty) + Expanded( + child: ListView.builder( + itemCount: musicbillList.length, + itemBuilder: (context, index) { + final musicbill = musicbillList[index]; + return ListTile( + leading: const Icon(Icons.list), + title: Text(musicbill.name), + onTap: () => Navigator.pushNamed( + context, + "/musicbill", + arguments: {"id": musicbill.id}, + ), + ); + }, + ), + ), + ], + ), + ); + } +} diff --git a/apps/flutter/lib/pages/musicbill/actions.dart b/apps/flutter/lib/pages/musicbill/actions.dart new file mode 100644 index 00000000..293d12bf --- /dev/null +++ b/apps/flutter/lib/pages/musicbill/actions.dart @@ -0,0 +1,31 @@ +import 'package:cicada/event_bus.dart'; +import 'package:cicada/states/musicbill.dart'; +import 'package:flutter/material.dart'; + +class Actions extends StatelessWidget { + final Musicbill musicbill; + const Actions({super.key, required this.musicbill}); + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsetsGeometry.all(10), + child: Row( + children: [ + IconButton( + onPressed: musicbill.musicList.isNotEmpty + ? () { + eventBus.fire( + AddMusicListToPlaylistEvent( + musicList: musicbill.musicList, + ), + ); + } + : null, + icon: Icon(Icons.playlist_add), + ), + ], + ), + ); + } +} diff --git a/apps/flutter/lib/pages/musicbill/index.dart b/apps/flutter/lib/pages/musicbill/index.dart new file mode 100644 index 00000000..57713a42 --- /dev/null +++ b/apps/flutter/lib/pages/musicbill/index.dart @@ -0,0 +1,69 @@ +import 'package:uuid/uuid.dart'; +import 'package:flutter/material.dart'; +import '../../utils/get_musicbill_by_id.dart'; +import '../../states/musicbill.dart' as musicbill_state; +import '../../event_bus.dart'; +import './actions.dart' as actions; + +const uuid = Uuid(); + +class Musicbill extends StatefulWidget { + final String id; + + const Musicbill({super.key, required this.id}); + + @override + State createState() => _MusicbillState(); +} + +class _MusicbillState extends State { + @override + void initState() { + super.initState(); + final musicbill = musicbill_state.musicbillState.musicbillList.firstWhere( + (m) => m.id == widget.id, + ); + if (musicbill.status != musicbill_state.MusicbillStatus.LOADING) { + Future.delayed( + Duration.zero, + () => musicbill_state.musicbillState.reloadMusicbill( + id: widget.id, + silence: + musicbill.status == musicbill_state.MusicbillStatus.SUCCESSFUL, + ), + ); + } + } + + @override + Widget build(BuildContext context) { + final musicbill = useMusicbillById(context, widget.id); + final empty = musicbill.musicList.isEmpty; + return Scaffold( + appBar: AppBar(title: Text(musicbill.name)), + body: Column( + children: [ + actions.Actions(musicbill: musicbill), + if (empty) + Expanded(child: Center(child: Text("No Music"))) + else + Expanded( + child: ListView.builder( + itemCount: musicbill.musicList.length, + itemBuilder: (context, index) { + final music = musicbill.musicList[index]; + return ListTile( + leading: const Icon(Icons.music_note_outlined), + title: Text(music.name), + onTap: () { + eventBus.fire(PlayMusicEvent(music: music)); + }, + ); + }, + ), + ), + ], + ), + ); + } +} diff --git a/apps/flutter/lib/play_indicator/index.dart b/apps/flutter/lib/play_indicator/index.dart new file mode 100644 index 00000000..16235b79 --- /dev/null +++ b/apps/flutter/lib/play_indicator/index.dart @@ -0,0 +1,16 @@ +import 'package:cicada/play_indicator/play_indicator.dart'; +import 'package:cicada/states/playqueue.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class PlayIndicatorContainer extends StatelessWidget { + const PlayIndicatorContainer({super.key}); + + @override + Widget build(BuildContext context) { + final currentMusic = context.watch().currentMusic; + return currentMusic == null + ? Container() + : PlayIndicator(playqueueMusic: currentMusic); + } +} diff --git a/apps/flutter/lib/play_indicator/play_indicator.dart b/apps/flutter/lib/play_indicator/play_indicator.dart new file mode 100644 index 00000000..a605eb80 --- /dev/null +++ b/apps/flutter/lib/play_indicator/play_indicator.dart @@ -0,0 +1,13 @@ +import 'package:cicada/states/playqueue.dart'; +import 'package:flutter/material.dart'; + +class PlayIndicator extends StatelessWidget { + final PlayqueueMusic playqueueMusic; + + const PlayIndicator({super.key, required this.playqueueMusic}); + + @override + Widget build(BuildContext context) { + return Text(playqueueMusic.music.name); + } +} diff --git a/apps/flutter/lib/server/api/get_musicbill.dart b/apps/flutter/lib/server/api/get_musicbill.dart new file mode 100644 index 00000000..7bd6d4dd --- /dev/null +++ b/apps/flutter/lib/server/api/get_musicbill.dart @@ -0,0 +1,28 @@ +import 'package:cicada/models/music.dart'; +import '../../utils/prefix_server_origin.dart'; +import '../request.dart'; + +class Musicbill { + String name; + String? cover; + List musicList; + + Musicbill({required this.name, required this.cover, required this.musicList}); + + factory Musicbill.fromJSON(Map json) => Musicbill( + name: json['name'], + cover: prefixServerOrigin(json['cover']), + musicList: (json['musicList'] as List) + .map((json) => Music.fromJSON(json)) + .toList(), + ); +} + +Future getMusicbill({required String id}) async { + final responseData = await httpGet( + path: "/api/musicbill", + query: {"id": id}, + withToken: true, + ); + return Musicbill.fromJSON(responseData); +} diff --git a/apps/flutter/lib/server/api/get_musicbill_list.dart b/apps/flutter/lib/server/api/get_musicbill_list.dart new file mode 100644 index 00000000..6e497273 --- /dev/null +++ b/apps/flutter/lib/server/api/get_musicbill_list.dart @@ -0,0 +1,26 @@ +import '../../utils/prefix_server_origin.dart'; +import '../request.dart'; + +class Musicbill { + final String id; + final String name; + final String? cover; + + Musicbill({required this.id, required this.name, required this.cover}); + + factory Musicbill.fromJSON(Map json) => Musicbill( + id: json['id'], + name: json['name'], + cover: prefixServerOrigin(json['cover']), + ); +} + +Future> getMusicbillList() async { + final responseData = await httpGet( + path: "/api/musicbill_list", + withToken: true, + ); + return (responseData as List) + .map((json) => Musicbill.fromJSON(json)) + .toList(); +} diff --git a/apps/flutter/lib/server/api/get_profile.dart b/apps/flutter/lib/server/api/get_profile.dart new file mode 100644 index 00000000..225824bc --- /dev/null +++ b/apps/flutter/lib/server/api/get_profile.dart @@ -0,0 +1,40 @@ +import 'package:cicada/utils/prefix_server_origin.dart'; + +import '../../constants/index.dart'; +import '../../server/request.dart'; + +class Profile { + final String id; + final String username; + final String? avatar; + final String nickname; + final int joinTimestamp; + final bool twoFAEnabled; + + Profile({ + required this.id, + required this.username, + required this.avatar, + required this.nickname, + required this.joinTimestamp, + required this.twoFAEnabled, + }); + + factory Profile.fromJSON(Map json) => Profile( + id: json['id'], + username: json['username'], + avatar: prefixServerOrigin(json['avatar']), + nickname: json['nickname'], + joinTimestamp: json['joinTimestamp'], + twoFAEnabled: json['twoFAEnabled'], + ); +} + +Future getProfile(String? token) async { + Map headers = {}; + if (token != null) { + headers[TOKEN_HEADER_KEY] = token; + } + final responseData = await httpGet(path: "/api/profile", headers: headers); + return Profile.fromJSON(responseData); +} diff --git a/apps/flutter/lib/server/base/get_captcha.dart b/apps/flutter/lib/server/base/get_captcha.dart new file mode 100644 index 00000000..81c9fb9a --- /dev/null +++ b/apps/flutter/lib/server/base/get_captcha.dart @@ -0,0 +1,16 @@ +import '../request.dart'; + +class Captcha { + final String id; + final String svg; + + Captcha({required this.id, required this.svg}); + + factory Captcha.fromJSON(Map json) => + Captcha(id: json['id'], svg: json['svg']); +} + +Future getCaptcha() async { + final responseData = await httpGet(path: "/base/captcha"); + return Captcha.fromJSON(responseData); +} diff --git a/apps/flutter/lib/server/base/get_metadata.dart b/apps/flutter/lib/server/base/get_metadata.dart new file mode 100644 index 00000000..1a06ae69 --- /dev/null +++ b/apps/flutter/lib/server/base/get_metadata.dart @@ -0,0 +1,20 @@ +import '../request.dart'; +import '../../states/server.dart'; + +class Metadata { + final String hostname; + final String version; + + Metadata({required this.hostname, required this.version}); + + factory Metadata.fromJSON(Map json) => + Metadata(hostname: json['hostname'], version: json['version']); +} + +Future getMetadata(String? origin) async { + final responseData = await httpGet( + origin: origin ?? serverState.currentServer!.origin, + path: "/base/metadata", + ); + return Metadata.fromJSON(responseData); +} diff --git a/apps/flutter/lib/server/base/login.dart b/apps/flutter/lib/server/base/login.dart new file mode 100644 index 00000000..079729e4 --- /dev/null +++ b/apps/flutter/lib/server/base/login.dart @@ -0,0 +1,19 @@ +import '../request.dart'; + +Future login({ + required String username, + required String password, + required String captchaId, + required String captchaValue, +}) async { + final token = await httpPost( + path: "/base/login", + data: { + "username": username, + "password": password, + "captchaId": captchaId, + "captchaValue": captchaValue, + }, + ); + return token; +} diff --git a/apps/flutter/lib/server/request.dart b/apps/flutter/lib/server/request.dart new file mode 100644 index 00000000..895b6923 --- /dev/null +++ b/apps/flutter/lib/server/request.dart @@ -0,0 +1,72 @@ +import '../constants/index.dart'; +import '../states/server.dart'; +import 'package:dio/dio.dart'; + +final dio = Dio(); + +class ResponseWrapper { + final String code; + final dynamic data; + + ResponseWrapper({required this.code, required this.data}); + + factory ResponseWrapper.fromJSON(Map json) => + ResponseWrapper(code: json['code'], data: json['data']); +} + +Map getTokenHeader(bool withToken) { + return { + TOKEN_HEADER_KEY: withToken ? serverState.currentUser?.token ?? "" : "", + }; +} + +Future handleResponse(Response response) async { + if (response.statusCode != 200) { + throw Exception( + "The server responsed with code \"${response.statusCode}\"", + ); + } + final responseData = ResponseWrapper.fromJSON(response.data); + if (responseData.code != 'success') { + throw Exception("The server responsed with code \"${responseData.code}\""); + } + return responseData.data; +} + +Future httpGet({ + required String path, + Map? query, + bool withToken = false, + String? origin, + Map? headers, +}) async { + final response = await dio.get( + '${origin ?? serverState.currentServer!.origin}$path', + queryParameters: query, + options: Options( + headers: {...getTokenHeader(withToken), ...(headers ?? {})}, + ), + ); + return handleResponse(response); +} + +Future httpPost({ + required String path, + Map? query, + Object? data, + bool withToken = false, + String? origin, +}) async { + final response = await dio.post( + '${origin ?? serverState.currentServer!.origin}$path', + queryParameters: query, + data: data, + options: Options( + headers: { + ...getTokenHeader(withToken), + "content-type": "application/json", + }, + ), + ); + return handleResponse(response); +} diff --git a/apps/flutter/lib/server_management/index.dart b/apps/flutter/lib/server_management/index.dart new file mode 100644 index 00000000..0b8a26c8 --- /dev/null +++ b/apps/flutter/lib/server_management/index.dart @@ -0,0 +1,136 @@ +import 'dart:collection'; +import '../extensions/list.dart'; +import '../server/base/get_metadata.dart'; +import '../states/server.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +typedef ServerEntry = DropdownMenuEntry; + +class ServerManagement extends StatefulWidget { + const ServerManagement({super.key}); + + @override + State createState() => _ServerManagementState(); +} + +class _ServerManagementState extends State { + late TextEditingController inputController; + + bool loading = false; + + @override + void initState() { + super.initState(); + inputController = TextEditingController(); + } + + @override + void dispose() { + inputController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final serverState = context.watch(); + return Scaffold( + body: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (serverState.serverList.isNotEmpty) + DropdownMenu( + onSelected: (server) => + server == null ? null : serverState.useServer(server.origin), + label: Text("Existed Servers"), + dropdownMenuEntries: UnmodifiableListView( + serverState.serverList.map( + (server) => ServerEntry( + label: '${server.hostname}(${server.origin})', + value: server, + ), + ), + ), + ), + ElevatedButton( + onPressed: () => serverState.clearServers(), + child: Text("Clear servers"), + ), + TextField( + readOnly: loading, + controller: inputController, + decoration: InputDecoration( + label: Text("Server Origin"), + hint: Text( + "https://cicada.example.com", + style: TextStyle(color: Colors.grey), + ), + border: OutlineInputBorder(), + ), + ), + ElevatedButton( + onPressed: loading + ? null + : () async { + final origin = inputController.text; + if (origin.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("Please enter server origin")), + ); + return; + } + + if (serverState.serverList.firstWhereOrNull( + (server) => server.origin == origin, + ) != + null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + "This server origin has already existed", + ), + ), + ); + return; + } + + setState(() { + loading = true; + }); + try { + final metadata = await getMetadata(origin); + serverState.addServer( + Server( + origin: origin, + hostname: metadata.hostname, + version: metadata.version, + users: [], + ), + ); + } catch (exception) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + "Failed to connect to \"$origin\" with exception \"${exception.toString()}\"", + ), + ), + ); + } + setState(() { + loading = false; + }); + }, + style: OutlinedButton.styleFrom(padding: EdgeInsets.all(20)), + child: loading + ? SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator(), + ) + : Text("Connect"), + ), + ], + ), + ); + } +} diff --git a/apps/flutter/lib/states/musicbill.dart b/apps/flutter/lib/states/musicbill.dart new file mode 100644 index 00000000..64bba1dc --- /dev/null +++ b/apps/flutter/lib/states/musicbill.dart @@ -0,0 +1,110 @@ +import 'package:cicada/models/music.dart'; +import 'package:cicada/models/singer.dart'; +import '../server/api/get_musicbill.dart' as get_musicbill; +import '../server/api/get_musicbill_list.dart'; +import 'package:flutter/material.dart'; + +enum MusicbillStatus { INITIAL, LOADING, SUCCESSFUL, FAILED } + +class Musicbill { + String id; + String name; + String? cover; + + List musicList = []; + MusicbillStatus status = MusicbillStatus.INITIAL; + + Musicbill({ + required this.id, + required this.name, + required this.cover, + + this.musicList = const [], + this.status = MusicbillStatus.INITIAL, + }); +} + +class MusicbillState extends ChangeNotifier { + bool loading = false; + Exception? exception; + List musicbillList = []; + + void reloadMusicbillList({required bool silence}) async { + exception = null; + loading = true; + notifyListeners(); + + try { + final data = await getMusicbillList(); + musicbillList = data + .map((m) => Musicbill(id: m.id, name: m.name, cover: m.cover)) + .toList(); + } catch (e) { + exception = e as Exception; + } + loading = false; + notifyListeners(); + } + + void reloadMusicbill({required String id, bool silence = true}) async { + if (!silence) { + musicbillList = musicbillList.map((musicbill) { + if (musicbill.id == id) { + musicbill.status = MusicbillStatus.LOADING; + } + return musicbill; + }).toList(); + notifyListeners(); + } + try { + final newMusicbill = await get_musicbill.getMusicbill(id: id); + musicbillList = musicbillList.map((musicbill) { + if (musicbill.id == id) { + return Musicbill( + id: id, + name: newMusicbill.name, + cover: newMusicbill.cover, + + musicList: newMusicbill.musicList + .map( + (music) => Music( + id: music.id, + name: music.name, + cover: music.cover, + asset: music.asset, + singers: music.singers + .map( + (s) => Singer( + id: s.id, + name: s.name, + aliases: s.aliases, + avatar: s.avatar, + ), + ) + .toList(), + ), + ) + .toList(), + status: MusicbillStatus.SUCCESSFUL, + ); + } + return musicbill; + }).toList(); + notifyListeners(); + } catch (e) { + /** + * @todo notification + * @author mebtte + */ + musicbillList = musicbillList.map((musicbill) { + if (musicbill.id == id) { + musicbill.status = MusicbillStatus.FAILED; + } + return musicbill; + }).toList(); + notifyListeners(); + } + } +} + +final musicbillState = MusicbillState(); diff --git a/apps/flutter/lib/states/playlist.dart b/apps/flutter/lib/states/playlist.dart new file mode 100644 index 00000000..c3e34e3a --- /dev/null +++ b/apps/flutter/lib/states/playlist.dart @@ -0,0 +1,47 @@ +import 'package:cicada/event_bus.dart'; +import 'package:cicada/models/music.dart'; +import 'package:flutter/material.dart'; +import 'package:uuid/uuid.dart'; + +final uuid = Uuid(); + +class PlaylistMusic { + final String pid; + final Music music; + + PlaylistMusic({required this.pid, required this.music}); +} + +class PlaylistState extends ChangeNotifier { + List playlist = []; + + void addMusicList(List musicList) { + final existedMusicIds = playlist.map((m) => m.music.id); + final unrepeatedMusicList = musicList + .where((m) => !existedMusicIds.contains(m.id)) + .toList(); + playlist.addAll( + unrepeatedMusicList.map( + (music) => PlaylistMusic(pid: uuid.v4(), music: music), + ), + ); + notifyListeners(); + } + + void Function() listen() { + final playMusicSubscription = eventBus.on().listen((event) { + addMusicList([event.music]); + }); + final addMusicListToPlaylistSubscription = eventBus + .on() + .listen((event) { + addMusicList(event.musicList); + }); + return () { + playMusicSubscription.cancel(); + addMusicListToPlaylistSubscription.cancel(); + }; + } +} + +final playlistState = PlaylistState(); diff --git a/apps/flutter/lib/states/playqueue.dart b/apps/flutter/lib/states/playqueue.dart new file mode 100644 index 00000000..a8bd7806 --- /dev/null +++ b/apps/flutter/lib/states/playqueue.dart @@ -0,0 +1,96 @@ +import 'dart:math'; + +import 'package:cicada/event_bus.dart'; +import 'package:cicada/states/playlist.dart'; +import 'package:flutter/material.dart'; +import 'package:uuid/uuid.dart'; +import '../extensions/list.dart'; +import '../models/music.dart'; + +final uuid = Uuid(); + +class PlayqueueMusic { + final String pid; + final Music music; + + PlayqueueMusic({required this.pid, required this.music}); +} + +class PlayqueueState extends ChangeNotifier { + int playqueueIndex = -1; + List playqueue = []; + + PlayqueueMusic? get currentMusic => playqueue.safeGet(playqueueIndex); + + void jump(Music music) { + final playqueueMusic = PlayqueueMusic(pid: uuid.v4(), music: music); + if (playqueueIndex == -1) { + playqueue = [playqueueMusic, ...playqueue]; + } else { + playqueue = [ + ...playqueue.sublist(0, playqueueIndex + 1), + playqueueMusic, + ...playqueue.sublist(playqueueIndex + 1), + ]; + } + notifyListeners(); + } + + void previous() { + final nextPlayqueueIndex = playqueueIndex - 1; + if (nextPlayqueueIndex < 0) { + /** + * @todo remind user there is no music in playqueue + * @author mebtte + */ + print("can not skip to previous"); + } else { + playqueueIndex = nextPlayqueueIndex; + notifyListeners(); + } + } + + void next() { + final nextPlayqueueIndex = playqueueIndex + 1; + if (nextPlayqueueIndex >= playqueue.length) { + final playlist = playlistState.playlist; + if (playlist.isEmpty) { + /** + * @todo remind user + * @author mebtte + */ + print("no musics in playlist"); + } else { + final random = Random(); + final playlistMusic = playlist[random.nextInt(playlist.length)]; + jump(playlistMusic.music); + next(); + } + } else { + playqueueIndex = nextPlayqueueIndex; + notifyListeners(); + } + } + + void Function() listen() { + final playMusicSubscription = eventBus.on().listen((event) { + jump(event.music); + next(); + }); + final addMusicListToPlaylistSubscription = eventBus + .on() + .listen((event) { + if (currentMusic == null) { + final random = Random(); + jump(event.musicList[random.nextInt(event.musicList.length)]); + next(); + } + }); + return () { + playMusicSubscription.cancel(); + addMusicListToPlaylistSubscription.cancel(); + }; + } +} + +final playqueueState = PlayqueueState(); diff --git a/apps/flutter/lib/states/server.dart b/apps/flutter/lib/states/server.dart new file mode 100644 index 00000000..cae8bac3 --- /dev/null +++ b/apps/flutter/lib/states/server.dart @@ -0,0 +1,157 @@ +import '../extensions/list.dart'; +import '../utils/preference.dart'; +import 'package:flutter/material.dart'; +import 'dart:convert'; + +class User { + String token; + String? avatar; + String id; + bool twoFAEnabled; + String username; + String nickname; + + User({ + required this.token, + required this.avatar, + required this.id, + required this.twoFAEnabled, + required this.username, + required this.nickname, + }); + + factory User.fromJSON(Map json) => User( + token: json['token'], + avatar: json['avatar'], + id: json['id'], + twoFAEnabled: json['twoFAEnabled'], + username: json['username'], + nickname: json['nickname'], + ); + + Map toJSON() => { + 'token': token, + 'avatar': avatar, + 'id': id, + 'twoFAEnabled': twoFAEnabled, + 'username': username, + 'nickname': nickname, + }; +} + +class Server { + String origin; + String hostname; + String version; + List users; + + Server({ + required this.origin, + required this.hostname, + required this.version, + required this.users, + }); + + factory Server.fromJSON(Map json) => Server( + origin: json['origin'], + hostname: json['hostname'], + version: json['version'], + users: (json['users'] as List) + .map((json) => User.fromJSON(json)) + .toList(), + ); + + Map toJSON() { + return { + 'origin': origin, + 'hostname': hostname, + 'version': version, + 'users': users.map((user) => user.toJSON()).toList(), + }; + } +} + +class ServerState extends ChangeNotifier { + List serverList = []; + String? selectedServerOrigin; + String? selectedUserId; + + Server? get currentServer => serverList.firstWhereOrNull( + (server) => server.origin == selectedServerOrigin, + ); + + User? get currentUser => currentServer?.users.firstWhereOrNull( + (user) => user.id == selectedUserId, + ); + + Map toJSON() { + return { + 'selectedServerOrigin': selectedServerOrigin, + 'selectedUserId': selectedUserId, + 'serverList': jsonEncode(serverList), + }; + } + + Future initialize() async { + final serverString = preference.instance.getString(StorageKey.SERVER); + if (serverString != null) { + final Map server = jsonDecode(serverString); + final undecodedServerList = + jsonDecode(server['serverList']) as List; + final serverList = undecodedServerList + .map((json) => Server.fromJSON(json)) + .toList(); + this.serverList = serverList; + selectedServerOrigin = server['selectedServerOrigin']; + selectedUserId = server['selectedUserId']; + } + + addListener(saveToPreference); + } + + void saveToPreference() { + preference.instance.setString(StorageKey.SERVER, jsonEncode(this)); + } + + void addServer(Server server) { + serverList.add(server); + selectedServerOrigin = server.origin; + notifyListeners(); + } + + void useServer(String origin) { + selectedServerOrigin = origin; + notifyListeners(); + } + + void reselectServer() { + selectedUserId = null; + selectedServerOrigin = null; + notifyListeners(); + } + + void clearServers() { + selectedUserId = null; + selectedServerOrigin = null; + serverList = []; + notifyListeners(); + } + + void addUser(User user) { + currentServer?.users.add(user); + selectedUserId = user.id; + notifyListeners(); + } + + void useUser(String id) { + selectedUserId = id; + notifyListeners(); + } + + void reselectUser() { + selectedUserId = null; + notifyListeners(); + } +} + +final serverState = ServerState(); diff --git a/apps/flutter/lib/user_management/index.dart b/apps/flutter/lib/user_management/index.dart new file mode 100644 index 00000000..0d5f273d --- /dev/null +++ b/apps/flutter/lib/user_management/index.dart @@ -0,0 +1,114 @@ +import '../../server/api/get_profile.dart'; +import '../../server/base/get_captcha.dart'; +import '../../server/base/login.dart'; +import '../../states/server.dart'; +import '../../widgets/captcha.dart'; +import 'package:flutter/material.dart'; + +class UserManagement extends StatefulWidget { + const UserManagement({super.key}); + + @override + State createState() => _UserManagementState(); +} + +class _UserManagementState extends State { + late TextEditingController usernameController; + late TextEditingController passwordController; + + @override + void initState() { + super.initState(); + usernameController = TextEditingController(); + passwordController = TextEditingController(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: () { + serverState.reselectServer(); + }, + child: Text("Change Server"), + ), + TextField( + controller: usernameController, + decoration: InputDecoration( + label: Text("Username"), + border: OutlineInputBorder(), + ), + ), + TextField( + obscureText: true, + controller: passwordController, + decoration: InputDecoration( + label: Text("Password"), + border: OutlineInputBorder(), + ), + ), + ElevatedButton( + onPressed: () { + final username = usernameController.text; + final password = passwordController.text; + if (username.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("Please enter username")), + ); + return; + } + if (password.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("Please enter password")), + ); + return; + } + showModalBottomSheet( + context: context, + builder: (context) => SizedBox( + width: double.infinity, + child: Center( + child: CaptchaWidget( + onContinue: + ({ + required Captcha captcha, + required String input, + }) async { + try { + final token = await login( + username: username, + password: password, + captchaId: captcha.id, + captchaValue: input, + ); + final profile = await getProfile(token); + serverState.addUser( + User( + token: token, + avatar: profile.avatar, + id: profile.id, + twoFAEnabled: profile.twoFAEnabled, + username: profile.username, + nickname: profile.nickname, + ), + ); + Navigator.pop(context); + } catch (e) { + print(e); + } + }, + ), + ), + ), + ); + }, + child: Text("Login"), + ), + ], + ), + ); + } +} diff --git a/apps/flutter/lib/utils/get_musicbill_by_id.dart b/apps/flutter/lib/utils/get_musicbill_by_id.dart new file mode 100644 index 00000000..7459aae8 --- /dev/null +++ b/apps/flutter/lib/utils/get_musicbill_by_id.dart @@ -0,0 +1,8 @@ +import '../states/musicbill.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +Musicbill useMusicbillById(BuildContext context, String id) { + final musicbillList = context.watch().musicbillList; + return musicbillList.firstWhere((m) => m.id == id); +} diff --git a/apps/flutter/lib/utils/preference.dart b/apps/flutter/lib/utils/preference.dart new file mode 100644 index 00000000..897d2d5d --- /dev/null +++ b/apps/flutter/lib/utils/preference.dart @@ -0,0 +1,17 @@ +import 'package:shared_preferences/shared_preferences.dart'; + +class StorageKey { + static const String SERVER = "server"; + static const String WINDOW_WIDTH = 'window-width'; + static const String WINDOW_HEIGHT = 'window-height'; +} + +class _Preference { + late SharedPreferences instance; + + Future initialize() async { + instance = await SharedPreferences.getInstance(); + } +} + +final preference = _Preference(); diff --git a/apps/flutter/lib/utils/prefix_server_origin.dart b/apps/flutter/lib/utils/prefix_server_origin.dart new file mode 100644 index 00000000..56904532 --- /dev/null +++ b/apps/flutter/lib/utils/prefix_server_origin.dart @@ -0,0 +1,5 @@ +import '../states/server.dart'; + +String? prefixServerOrigin(String? asset) { + return asset == null ? null : "${serverState.currentServer!.origin}$asset"; +} diff --git a/apps/flutter/lib/widgets/captcha.dart b/apps/flutter/lib/widgets/captcha.dart new file mode 100644 index 00000000..0b0a411f --- /dev/null +++ b/apps/flutter/lib/widgets/captcha.dart @@ -0,0 +1,107 @@ +import '../server/base/get_captcha.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +class CaptchaWidget extends StatefulWidget { + final Future Function({required Captcha captcha, required String input}) + onContinue; + + const CaptchaWidget({super.key, required this.onContinue}); + + @override + State createState() => _CaptchaWidgetState(); +} + +class _CaptchaWidgetState extends State { + Exception? exception; + Captcha? captcha; + bool loading = false; + + late TextEditingController inputController; + + @override + void initState() { + super.initState(); + inputController = TextEditingController(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + getCaptchaWrapper(); + } + + Future getCaptchaWrapper() async { + setState(() { + exception = null; + captcha = null; + }); + try { + final captcha = await getCaptcha(); + setState(() { + this.captcha = captcha; + }); + } catch (e) { + setState(() { + exception = e as Exception; + }); + } + } + + @override + Widget build(BuildContext context) { + return exception == null + ? captcha == null + ? CircularProgressIndicator() + : Column( + children: [ + SvgPicture.string(captcha!.svg), + TextField( + readOnly: loading, + controller: inputController, + decoration: InputDecoration( + label: Text("Captcha"), + border: OutlineInputBorder(), + ), + ), + loading + ? CircularProgressIndicator() + : ElevatedButton( + onPressed: () async { + final input = inputController.text; + if (input.isEmpty) { + ScaffoldMessenger.of( + Navigator.of( + context, + rootNavigator: true, + ).context, + ).showSnackBar( + SnackBar( + content: Text("Please enter captcha"), + ), + ); + return; + } + + setState(() { + loading = true; + }); + try { + await widget.onContinue( + captcha: captcha!, + input: input, + ); + } catch (e) { + Navigator.pop(context); + } + setState(() { + loading = false; + }); + }, + child: Text("Continue"), + ), + ], + ) + : Center(child: Text(exception.toString())); + } +} diff --git a/apps/flutter/lib/window_manager.dart b/apps/flutter/lib/window_manager.dart new file mode 100644 index 00000000..dd84a837 --- /dev/null +++ b/apps/flutter/lib/window_manager.dart @@ -0,0 +1,41 @@ +import './utils/preference.dart'; +import 'package:flutter/material.dart'; +import 'package:window_manager/window_manager.dart'; + +class MyWindowListener extends WindowListener { + @override + Future onWindowClose() async { + windowManager.hide(); + } + + @override + Future onWindowResized() async { + final size = await windowManager.getSize(); + await preference.instance.setDouble(StorageKey.WINDOW_WIDTH, size.width); + await preference.instance.setDouble(StorageKey.WINDOW_HEIGHT, size.height); + } +} + +Future initializeWindow() async { + await windowManager.ensureInitialized(); + windowManager.waitUntilReadyToShow( + WindowOptions( + size: Size( + preference.instance.getDouble(StorageKey.WINDOW_WIDTH) ?? 800, + preference.instance.getDouble(StorageKey.WINDOW_HEIGHT) ?? 600, + ), + minimumSize: Size(350, 600), + center: true, + backgroundColor: Colors.transparent, + skipTaskbar: false, + titleBarStyle: TitleBarStyle.hidden, + title: "Cicada", + windowButtonVisibility: false, + ), + () async { + await windowManager.show(); + await windowManager.focus(); + }, + ); + windowManager.addListener(MyWindowListener()); +} diff --git a/apps/flutter/linux/.gitignore b/apps/flutter/linux/.gitignore new file mode 100644 index 00000000..d3896c98 --- /dev/null +++ b/apps/flutter/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/apps/flutter/linux/CMakeLists.txt b/apps/flutter/linux/CMakeLists.txt new file mode 100644 index 00000000..6b1a44f1 --- /dev/null +++ b/apps/flutter/linux/CMakeLists.txt @@ -0,0 +1,128 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "Cicada") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.mebtte.cicada") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/apps/flutter/linux/flutter/CMakeLists.txt b/apps/flutter/linux/flutter/CMakeLists.txt new file mode 100644 index 00000000..d5bd0164 --- /dev/null +++ b/apps/flutter/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/apps/flutter/linux/flutter/generated_plugin_registrant.cc b/apps/flutter/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..c8f3dcc2 --- /dev/null +++ b/apps/flutter/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,19 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include + +void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) screen_retriever_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverLinuxPlugin"); + screen_retriever_linux_plugin_register_with_registrar(screen_retriever_linux_registrar); + g_autoptr(FlPluginRegistrar) window_manager_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin"); + window_manager_plugin_register_with_registrar(window_manager_registrar); +} diff --git a/apps/flutter/linux/flutter/generated_plugin_registrant.h b/apps/flutter/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..e0f0a47b --- /dev/null +++ b/apps/flutter/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/apps/flutter/linux/flutter/generated_plugins.cmake b/apps/flutter/linux/flutter/generated_plugins.cmake new file mode 100644 index 00000000..00303ac7 --- /dev/null +++ b/apps/flutter/linux/flutter/generated_plugins.cmake @@ -0,0 +1,25 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + screen_retriever_linux + window_manager +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/apps/flutter/linux/runner/CMakeLists.txt b/apps/flutter/linux/runner/CMakeLists.txt new file mode 100644 index 00000000..e97dabc7 --- /dev/null +++ b/apps/flutter/linux/runner/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the application ID. +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") diff --git a/apps/flutter/linux/runner/main.cc b/apps/flutter/linux/runner/main.cc new file mode 100644 index 00000000..e7c5c543 --- /dev/null +++ b/apps/flutter/linux/runner/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/apps/flutter/linux/runner/my_application.cc b/apps/flutter/linux/runner/my_application.cc new file mode 100644 index 00000000..83d30edb --- /dev/null +++ b/apps/flutter/linux/runner/my_application.cc @@ -0,0 +1,133 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char **dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication *application) { + MyApplication *self = MY_APPLICATION(application); + GtkWindow *window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen *screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar *wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar *header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "Cicada"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "Cicada"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments( + project, self->dart_entrypoint_arguments); + + FlView *view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication *application, + gchar ***arguments, + int *exit_status) { + MyApplication *self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GApplication::startup. +static void my_application_startup(GApplication *application) { + // MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication *application) { + // MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject *object) { + MyApplication *self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass *klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = + my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication *self) {} + +MyApplication *my_application_new() { + // Set the program name to the application ID, which helps various systems + // like GTK and desktop environments map this running application to its + // corresponding .desktop file. This ensures better integration by allowing + // the application to be recognized beyond its binary name. + g_set_prgname(APPLICATION_ID); + + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, "flags", + G_APPLICATION_NON_UNIQUE, nullptr)); +} diff --git a/apps/flutter/linux/runner/my_application.h b/apps/flutter/linux/runner/my_application.h new file mode 100644 index 00000000..72271d5e --- /dev/null +++ b/apps/flutter/linux/runner/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/apps/flutter/macos/.gitignore b/apps/flutter/macos/.gitignore new file mode 100644 index 00000000..746adbb6 --- /dev/null +++ b/apps/flutter/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/apps/flutter/macos/Flutter/Flutter-Debug.xcconfig b/apps/flutter/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 00000000..4b81f9b2 --- /dev/null +++ b/apps/flutter/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/apps/flutter/macos/Flutter/Flutter-Release.xcconfig b/apps/flutter/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 00000000..5caa9d15 --- /dev/null +++ b/apps/flutter/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/apps/flutter/macos/Flutter/GeneratedPluginRegistrant.swift b/apps/flutter/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 00000000..e2a309d6 --- /dev/null +++ b/apps/flutter/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,26 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import audio_service +import audio_session +import just_audio +import path_provider_foundation +import screen_retriever_macos +import shared_preferences_foundation +import sqflite_darwin +import window_manager + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + AudioServicePlugin.register(with: registry.registrar(forPlugin: "AudioServicePlugin")) + AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin")) + JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) + WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) +} diff --git a/apps/flutter/macos/Podfile b/apps/flutter/macos/Podfile new file mode 100644 index 00000000..ff5ddb3b --- /dev/null +++ b/apps/flutter/macos/Podfile @@ -0,0 +1,42 @@ +platform :osx, '10.15' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/apps/flutter/macos/Podfile.lock b/apps/flutter/macos/Podfile.lock new file mode 100644 index 00000000..b8a0a88f --- /dev/null +++ b/apps/flutter/macos/Podfile.lock @@ -0,0 +1,69 @@ +PODS: + - audio_service (0.0.1): + - Flutter + - FlutterMacOS + - audio_session (0.0.1): + - FlutterMacOS + - FlutterMacOS (1.0.0) + - just_audio (0.0.1): + - Flutter + - FlutterMacOS + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + - screen_retriever_macos (0.0.1): + - FlutterMacOS + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS + - sqflite_darwin (0.0.4): + - Flutter + - FlutterMacOS + - window_manager (0.5.0): + - FlutterMacOS + +DEPENDENCIES: + - audio_service (from `Flutter/ephemeral/.symlinks/plugins/audio_service/darwin`) + - audio_session (from `Flutter/ephemeral/.symlinks/plugins/audio_session/macos`) + - FlutterMacOS (from `Flutter/ephemeral`) + - just_audio (from `Flutter/ephemeral/.symlinks/plugins/just_audio/darwin`) + - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) + - screen_retriever_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos`) + - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) + - sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`) + - window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`) + +EXTERNAL SOURCES: + audio_service: + :path: Flutter/ephemeral/.symlinks/plugins/audio_service/darwin + audio_session: + :path: Flutter/ephemeral/.symlinks/plugins/audio_session/macos + FlutterMacOS: + :path: Flutter/ephemeral + just_audio: + :path: Flutter/ephemeral/.symlinks/plugins/just_audio/darwin + path_provider_foundation: + :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin + screen_retriever_macos: + :path: Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos + shared_preferences_foundation: + :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin + sqflite_darwin: + :path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin + window_manager: + :path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos + +SPEC CHECKSUMS: + audio_service: aa99a6ba2ae7565996015322b0bb024e1d25c6fd + audio_session: eaca2512cf2b39212d724f35d11f46180ad3a33e + FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 + just_audio: 4e391f57b79cad2b0674030a00453ca5ce817eed + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + screen_retriever_macos: 452e51764a9e1cdb74b3c541238795849f21557f + shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 + sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 + window_manager: b729e31d38fb04905235df9ea896128991cad99e + +PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009 + +COCOAPODS: 1.16.2 diff --git a/apps/flutter/macos/Runner.xcodeproj/project.pbxproj b/apps/flutter/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..cd8698d3 --- /dev/null +++ b/apps/flutter/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,801 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 6C729DB2AB0B4DA3E92BA63B /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0972C3247E380FFD5A34885 /* Pods_Runner.framework */; }; + A86471E8ADAFEEA2EA4A4D69 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1BA7AF8440E369001EB1B06D /* Pods_RunnerTests.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1791270FB5B2699E168847D6 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 1BA7AF8440E369001EB1B06D /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* Cicada.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Cicada.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 5ED111CA4716EA15D1C4913D /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 73893E6F81E0ABFECCCE1850 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 868B4B2AEDA81F31933D1379 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + BF25588A9C1B192EAF743D2E /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + C0972C3247E380FFD5A34885 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + F76DAF2F011A2BB2015CF410 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A86471E8ADAFEEA2EA4A4D69 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 6C729DB2AB0B4DA3E92BA63B /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 252316AA56C89A629032AE11 /* Pods */ = { + isa = PBXGroup; + children = ( + 868B4B2AEDA81F31933D1379 /* Pods-Runner.debug.xcconfig */, + F76DAF2F011A2BB2015CF410 /* Pods-Runner.release.xcconfig */, + 1791270FB5B2699E168847D6 /* Pods-Runner.profile.xcconfig */, + 73893E6F81E0ABFECCCE1850 /* Pods-RunnerTests.debug.xcconfig */, + BF25588A9C1B192EAF743D2E /* Pods-RunnerTests.release.xcconfig */, + 5ED111CA4716EA15D1C4913D /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + 252316AA56C89A629032AE11 /* Pods */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* Cicada.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + C0972C3247E380FFD5A34885 /* Pods_Runner.framework */, + 1BA7AF8440E369001EB1B06D /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 26DEC300BD423B453280101E /* [CP] Check Pods Manifest.lock */, + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 63A0D7E7582C3E630C515DA5 /* [CP] Check Pods Manifest.lock */, + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ADF5B627EBB2011285560DD2 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* Cicada.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 26DEC300BD423B453280101E /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; + 63A0D7E7582C3E630C515DA5 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + ADF5B627EBB2011285560DD2 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 73893E6F81E0ABFECCCE1850 /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.mebtte.cicada.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Cicada.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Cicada"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = BF25588A9C1B192EAF743D2E /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.mebtte.cicada.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Cicada.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Cicada"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5ED111CA4716EA15D1C4913D /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.mebtte.cicada.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Cicada.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Cicada"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/apps/flutter/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/apps/flutter/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/apps/flutter/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/apps/flutter/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/apps/flutter/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..f3633860 --- /dev/null +++ b/apps/flutter/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/flutter/macos/Runner.xcworkspace/contents.xcworkspacedata b/apps/flutter/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..21a3cc14 --- /dev/null +++ b/apps/flutter/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/apps/flutter/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/apps/flutter/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/apps/flutter/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/apps/flutter/macos/Runner/AppDelegate.swift b/apps/flutter/macos/Runner/AppDelegate.swift new file mode 100644 index 00000000..0c358c9e --- /dev/null +++ b/apps/flutter/macos/Runner/AppDelegate.swift @@ -0,0 +1,28 @@ +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return false + } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } + + override func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) + -> Bool + { + if !flag { + for window in NSApp.windows { + if !window.isVisible { + window.setIsVisible(true) + } + window.makeKeyAndOrderFront(self) + NSApp.activate(ignoringOtherApps: true) + } + } + return true + } +} diff --git a/apps/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/apps/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..60e60c94 --- /dev/null +++ b/apps/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images": [ + { + "size": "16x16", + "idiom": "mac", + "filename": "app_icon_16.png", + "scale": "1x" + }, + { + "size": "16x16", + "idiom": "mac", + "filename": "app_icon_32.png", + "scale": "2x" + }, + { + "size": "32x32", + "idiom": "mac", + "filename": "app_icon_32.png", + "scale": "1x" + }, + { + "size": "32x32", + "idiom": "mac", + "filename": "app_icon_64.png", + "scale": "2x" + }, + { + "size": "128x128", + "idiom": "mac", + "filename": "app_icon_128.png", + "scale": "1x" + }, + { + "size": "128x128", + "idiom": "mac", + "filename": "app_icon_256.png", + "scale": "2x" + }, + { + "size": "256x256", + "idiom": "mac", + "filename": "app_icon_256.png", + "scale": "1x" + }, + { + "size": "256x256", + "idiom": "mac", + "filename": "app_icon_512.png", + "scale": "2x" + }, + { + "size": "512x512", + "idiom": "mac", + "filename": "app_icon_512.png", + "scale": "1x" + }, + { + "size": "512x512", + "idiom": "mac", + "filename": "app_icon_1024.png", + "scale": "2x" + } + ], + "info": { + "version": 1, + "author": "xcode" + } +} diff --git a/apps/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/apps/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 00000000..82b6f9d9 Binary files /dev/null and b/apps/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/apps/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/apps/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 00000000..13b35eba Binary files /dev/null and b/apps/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/apps/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/apps/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 00000000..0a3f5fa4 Binary files /dev/null and b/apps/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/apps/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/apps/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 00000000..bdb57226 Binary files /dev/null and b/apps/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/apps/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/apps/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 00000000..f083318e Binary files /dev/null and b/apps/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/apps/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/apps/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 00000000..326c0e72 Binary files /dev/null and b/apps/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/apps/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/apps/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 00000000..2f1632cf Binary files /dev/null and b/apps/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/apps/flutter/macos/Runner/Base.lproj/MainMenu.xib b/apps/flutter/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 00000000..80e867a4 --- /dev/null +++ b/apps/flutter/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/flutter/macos/Runner/Configs/AppInfo.xcconfig b/apps/flutter/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 00000000..d6575592 --- /dev/null +++ b/apps/flutter/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = Cicada + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.mebtte.cicada + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2025 com.mebtte. All rights reserved. diff --git a/apps/flutter/macos/Runner/Configs/Debug.xcconfig b/apps/flutter/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 00000000..36b0fd94 --- /dev/null +++ b/apps/flutter/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/apps/flutter/macos/Runner/Configs/Release.xcconfig b/apps/flutter/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 00000000..dff4f495 --- /dev/null +++ b/apps/flutter/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/apps/flutter/macos/Runner/Configs/Warnings.xcconfig b/apps/flutter/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 00000000..42bcbf47 --- /dev/null +++ b/apps/flutter/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/apps/flutter/macos/Runner/DebugProfile.entitlements b/apps/flutter/macos/Runner/DebugProfile.entitlements new file mode 100644 index 00000000..5a5e97c0 --- /dev/null +++ b/apps/flutter/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + com.apple.security.network.client + + + diff --git a/apps/flutter/macos/Runner/Info.plist b/apps/flutter/macos/Runner/Info.plist new file mode 100644 index 00000000..1c54d76a --- /dev/null +++ b/apps/flutter/macos/Runner/Info.plist @@ -0,0 +1,39 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSAllowsArbitraryLoadsForMedia + + + + diff --git a/apps/flutter/macos/Runner/MainFlutterWindow.swift b/apps/flutter/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 00000000..3cc05eb2 --- /dev/null +++ b/apps/flutter/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/apps/flutter/macos/Runner/Release.entitlements b/apps/flutter/macos/Runner/Release.entitlements new file mode 100644 index 00000000..1065041b --- /dev/null +++ b/apps/flutter/macos/Runner/Release.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.network.client + + + diff --git a/apps/flutter/macos/RunnerTests/RunnerTests.swift b/apps/flutter/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..61f3bd1f --- /dev/null +++ b/apps/flutter/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/apps/flutter/pubspec.lock b/apps/flutter/pubspec.lock new file mode 100644 index 00000000..6b99055d --- /dev/null +++ b/apps/flutter/pubspec.lock @@ -0,0 +1,722 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" + audio_service: + dependency: "direct main" + description: + name: audio_service + sha256: cb122c7c2639d2a992421ef96b67948ad88c5221da3365ccef1031393a76e044 + url: "https://pub.dev" + source: hosted + version: "0.18.18" + audio_service_platform_interface: + dependency: transitive + description: + name: audio_service_platform_interface + sha256: "6283782851f6c8b501b60904a32fc7199dc631172da0629d7301e66f672ab777" + url: "https://pub.dev" + source: hosted + version: "0.1.3" + audio_service_web: + dependency: transitive + description: + name: audio_service_web + sha256: b8ea9243201ee53383157fbccf13d5d2a866b5dda922ec19d866d1d5d70424df + url: "https://pub.dev" + source: hosted + version: "0.1.4" + audio_session: + dependency: transitive + description: + name: audio_session + sha256: "8f96a7fecbb718cb093070f868b4cdcb8a9b1053dce342ff8ab2fde10eb9afb7" + url: "https://pub.dev" + source: hosted + version: "0.2.2" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + dio: + dependency: "direct main" + description: + name: dio + sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9 + url: "https://pub.dev" + source: hosted + version: "5.9.0" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + event_bus: + dependency: "direct main" + description: + name: event_bus + sha256: "1a55e97923769c286d295240048fc180e7b0768902c3c2e869fe059aafa15304" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" + url: "https://pub.dev" + source: hosted + version: "3.4.1" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + sha256: cd57f7969b4679317c17af6fd16ee233c1e60a82ed209d8a475c54fd6fd6f845 + url: "https://pub.dev" + source: hosted + version: "2.2.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + get_it: + dependency: "direct main" + description: + name: get_it + sha256: a4292e7cf67193f8e7c1258203104eb2a51ec8b3a04baa14695f4064c144297b + url: "https://pub.dev" + source: hosted + version: "8.2.0" + http: + dependency: transitive + description: + name: http + sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007 + url: "https://pub.dev" + source: hosted + version: "1.5.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + js: + dependency: transitive + description: + name: js + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" + url: "https://pub.dev" + source: hosted + version: "0.7.2" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + just_audio: + dependency: "direct main" + description: + name: just_audio + sha256: "679637a3ec5b6e00f36472f5a3663667df00ee4822cbf5dafca0f568c710960a" + url: "https://pub.dev" + source: hosted + version: "0.10.4" + just_audio_platform_interface: + dependency: transitive + description: + name: just_audio_platform_interface + sha256: "2532c8d6702528824445921c5ff10548b518b13f808c2e34c2fd54793b999a6a" + url: "https://pub.dev" + source: hosted + version: "4.6.0" + just_audio_web: + dependency: transitive + description: + name: just_audio_web + sha256: "6ba8a2a7e87d57d32f0f7b42856ade3d6a9fbe0f1a11fabae0a4f00bb73f0663" + url: "https://pub.dev" + source: hosted + version: "0.4.16" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "8dcda04c3fc16c14f48a7bb586d4be1f0d1572731b6d81d51772ef47c02081e0" + url: "https://pub.dev" + source: hosted + version: "11.0.1" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + url: "https://pub.dev" + source: hosted + version: "5.1.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 + url: "https://pub.dev" + source: hosted + version: "2.2.17" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" + url: "https://pub.dev" + source: hosted + version: "7.0.1" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + provider: + dependency: "direct main" + description: + name: provider + sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84" + url: "https://pub.dev" + source: hosted + version: "6.1.5" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" + url: "https://pub.dev" + source: hosted + version: "0.28.0" + screen_retriever: + dependency: transitive + description: + name: screen_retriever + sha256: "570dbc8e4f70bac451e0efc9c9bb19fa2d6799a11e6ef04f946d7886d2e23d0c" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_linux: + dependency: transitive + description: + name: screen_retriever_linux + sha256: f7f8120c92ef0784e58491ab664d01efda79a922b025ff286e29aa123ea3dd18 + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_macos: + dependency: transitive + description: + name: screen_retriever_macos + sha256: "71f956e65c97315dd661d71f828708bd97b6d358e776f1a30d5aa7d22d78a149" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_platform_interface: + dependency: transitive + description: + name: screen_retriever_platform_interface + sha256: ee197f4581ff0d5608587819af40490748e1e39e648d7680ecf95c05197240c0 + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_windows: + dependency: transitive + description: + name: screen_retriever_windows + sha256: "449ee257f03ca98a57288ee526a301a430a344a161f9202b4fcc38576716fe13" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + url: "https://pub.dev" + source: hosted + version: "2.5.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "5bcf0772a761b04f8c6bf814721713de6f3e5d9d89caf8d3fe031b02a342379e" + url: "https://pub.dev" + source: hosted + version: "2.4.11" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev" + source: hosted + version: "1.10.1" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 + url: "https://pub.dev" + source: hosted + version: "3.4.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + url: "https://pub.dev" + source: hosted + version: "0.7.6" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + uuid: + dependency: "direct main" + description: + name: uuid + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + url: "https://pub.dev" + source: hosted + version: "4.5.1" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6 + url: "https://pub.dev" + source: hosted + version: "1.1.19" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" + url: "https://pub.dev" + source: hosted + version: "1.1.13" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc + url: "https://pub.dev" + source: hosted + version: "1.1.19" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" + url: "https://pub.dev" + source: hosted + version: "15.0.2" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + window_manager: + dependency: "direct main" + description: + name: window_manager + sha256: "7eb6d6c4164ec08e1bf978d6e733f3cebe792e2a23fb07cbca25c2872bfdbdcd" + url: "https://pub.dev" + source: hosted + version: "0.5.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + url: "https://pub.dev" + source: hosted + version: "6.6.1" +sdks: + dart: ">=3.8.1 <4.0.0" + flutter: ">=3.29.0" diff --git a/apps/flutter/pubspec.yaml b/apps/flutter/pubspec.yaml new file mode 100644 index 00000000..ba34462a --- /dev/null +++ b/apps/flutter/pubspec.yaml @@ -0,0 +1,29 @@ +name: cicada +description: "Client for cicada." +publish_to: "none" +version: 0.1.0 + +environment: + sdk: ^3.8.1 + +dependencies: + audio_service: ^0.18.18 + dio: ^5.9.0 + event_bus: ^2.0.1 + flutter: + sdk: flutter + flutter_svg: ^2.2.0 + get_it: ^8.2.0 + just_audio: ^0.10.4 + provider: ^6.1.5 + shared_preferences: ^2.5.3 + uuid: ^4.5.1 + window_manager: ^0.5.1 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 + +flutter: + uses-material-design: true diff --git a/apps/flutter/web/favicon.png b/apps/flutter/web/favicon.png new file mode 100644 index 00000000..8aaa46ac Binary files /dev/null and b/apps/flutter/web/favicon.png differ diff --git a/apps/flutter/web/icons/Icon-192.png b/apps/flutter/web/icons/Icon-192.png new file mode 100644 index 00000000..b749bfef Binary files /dev/null and b/apps/flutter/web/icons/Icon-192.png differ diff --git a/apps/flutter/web/icons/Icon-512.png b/apps/flutter/web/icons/Icon-512.png new file mode 100644 index 00000000..88cfd48d Binary files /dev/null and b/apps/flutter/web/icons/Icon-512.png differ diff --git a/apps/flutter/web/icons/Icon-maskable-192.png b/apps/flutter/web/icons/Icon-maskable-192.png new file mode 100644 index 00000000..eb9b4d76 Binary files /dev/null and b/apps/flutter/web/icons/Icon-maskable-192.png differ diff --git a/apps/flutter/web/icons/Icon-maskable-512.png b/apps/flutter/web/icons/Icon-maskable-512.png new file mode 100644 index 00000000..d69c5669 Binary files /dev/null and b/apps/flutter/web/icons/Icon-maskable-512.png differ diff --git a/apps/flutter/web/index.html b/apps/flutter/web/index.html new file mode 100644 index 00000000..9de9a91b --- /dev/null +++ b/apps/flutter/web/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + Cicada + + + + + + diff --git a/apps/flutter/web/manifest.json b/apps/flutter/web/manifest.json new file mode 100644 index 00000000..14510387 --- /dev/null +++ b/apps/flutter/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "Cicada", + "short_name": "Cicada", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/apps/flutter/windows/.gitignore b/apps/flutter/windows/.gitignore new file mode 100644 index 00000000..d492d0d9 --- /dev/null +++ b/apps/flutter/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/apps/flutter/windows/CMakeLists.txt b/apps/flutter/windows/CMakeLists.txt new file mode 100644 index 00000000..625b9653 --- /dev/null +++ b/apps/flutter/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(cicada LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "Cicada") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/apps/flutter/windows/flutter/CMakeLists.txt b/apps/flutter/windows/flutter/CMakeLists.txt new file mode 100644 index 00000000..903f4899 --- /dev/null +++ b/apps/flutter/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/apps/flutter/windows/flutter/generated_plugin_registrant.cc b/apps/flutter/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..c6fe39a5 --- /dev/null +++ b/apps/flutter/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,17 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi")); + WindowManagerPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("WindowManagerPlugin")); +} diff --git a/apps/flutter/windows/flutter/generated_plugin_registrant.h b/apps/flutter/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..dc139d85 --- /dev/null +++ b/apps/flutter/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/apps/flutter/windows/flutter/generated_plugins.cmake b/apps/flutter/windows/flutter/generated_plugins.cmake new file mode 100644 index 00000000..5e3bc3d8 --- /dev/null +++ b/apps/flutter/windows/flutter/generated_plugins.cmake @@ -0,0 +1,25 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + screen_retriever_windows + window_manager +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/apps/flutter/windows/runner/CMakeLists.txt b/apps/flutter/windows/runner/CMakeLists.txt new file mode 100644 index 00000000..394917c0 --- /dev/null +++ b/apps/flutter/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/apps/flutter/windows/runner/Runner.rc b/apps/flutter/windows/runner/Runner.rc new file mode 100644 index 00000000..3c014428 --- /dev/null +++ b/apps/flutter/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.mebtte" "\0" + VALUE "FileDescription", "Cicada" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "Cicada" "\0" + VALUE "LegalCopyright", "Copyright (C) 2025 com.mebtte. All rights reserved." "\0" + VALUE "OriginalFilename", "Cicada.exe" "\0" + VALUE "ProductName", "Cicada" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/apps/flutter/windows/runner/flutter_window.cpp b/apps/flutter/windows/runner/flutter_window.cpp new file mode 100644 index 00000000..955ee303 --- /dev/null +++ b/apps/flutter/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/apps/flutter/windows/runner/flutter_window.h b/apps/flutter/windows/runner/flutter_window.h new file mode 100644 index 00000000..6da0652f --- /dev/null +++ b/apps/flutter/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/apps/flutter/windows/runner/main.cpp b/apps/flutter/windows/runner/main.cpp new file mode 100644 index 00000000..6334958c --- /dev/null +++ b/apps/flutter/windows/runner/main.cpp @@ -0,0 +1,42 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"Cicada", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/apps/flutter/windows/runner/resource.h b/apps/flutter/windows/runner/resource.h new file mode 100644 index 00000000..66a65d1e --- /dev/null +++ b/apps/flutter/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/apps/flutter/windows/runner/resources/app_icon.ico b/apps/flutter/windows/runner/resources/app_icon.ico new file mode 100644 index 00000000..c04e20ca Binary files /dev/null and b/apps/flutter/windows/runner/resources/app_icon.ico differ diff --git a/apps/flutter/windows/runner/runner.exe.manifest b/apps/flutter/windows/runner/runner.exe.manifest new file mode 100644 index 00000000..153653e8 --- /dev/null +++ b/apps/flutter/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/apps/flutter/windows/runner/utils.cpp b/apps/flutter/windows/runner/utils.cpp new file mode 100644 index 00000000..3a0b4651 --- /dev/null +++ b/apps/flutter/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + unsigned int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/apps/flutter/windows/runner/utils.h b/apps/flutter/windows/runner/utils.h new file mode 100644 index 00000000..3879d547 --- /dev/null +++ b/apps/flutter/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/apps/flutter/windows/runner/win32_window.cpp b/apps/flutter/windows/runner/win32_window.cpp new file mode 100644 index 00000000..60608d0f --- /dev/null +++ b/apps/flutter/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/apps/flutter/windows/runner/win32_window.h b/apps/flutter/windows/runner/win32_window.h new file mode 100644 index 00000000..e901dde6 --- /dev/null +++ b/apps/flutter/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/apps/pwa/src/components/empty.tsx b/apps/pwa/src/components/empty.tsx index e9e1ed57..140aa69c 100644 --- a/apps/pwa/src/components/empty.tsx +++ b/apps/pwa/src/components/empty.tsx @@ -50,6 +50,7 @@ function Empty({ src={emptyImage} alt="empty" crossOrigin="anonymous" + draggable={false} />
{description}
diff --git a/apps/pwa/src/components/icon_button.tsx b/apps/pwa/src/components/icon_button.tsx index 71b450fb..30cfffc7 100644 --- a/apps/pwa/src/components/icon_button.tsx +++ b/apps/pwa/src/components/icon_button.tsx @@ -1,9 +1,4 @@ -import { - ButtonHTMLAttributes, - CSSProperties, - ForwardedRef, - forwardRef, -} from 'react'; +import { ButtonHTMLAttributes, CSSProperties, forwardRef } from 'react'; import styled, { css } from 'styled-components'; import { ComponentSize } from '../constants/style'; import { CSSVariable } from '../global_style'; @@ -60,30 +55,34 @@ const spinnerStyle: CSSProperties = { transform: 'translate(-50%, -50%)', }; -type Props = ButtonHTMLAttributes & { - size?: number; - loading?: boolean; -}; - -function IconButton( - { - size = ComponentSize.NORMAL, - loading = false, - disabled = false, - children, - ...props - }: Props, - ref: ForwardedRef, -) { - return ( - - ); -} +const IconButton = forwardRef< + HTMLButtonElement, + ButtonHTMLAttributes & { + size?: number; + loading?: boolean; + } +>( + ( + { + size = ComponentSize.NORMAL, + loading = false, + disabled = false, + children, + ...props + }, + ref, + ) => { + return ( + + ); + }, +); +IconButton.displayName = 'IconButton'; -export default forwardRef(IconButton); +export default IconButton; diff --git a/apps/pwa/src/components/menu_item.tsx b/apps/pwa/src/components/menu_item.tsx index 9ca11b91..feae588e 100644 --- a/apps/pwa/src/components/menu_item.tsx +++ b/apps/pwa/src/components/menu_item.tsx @@ -17,6 +17,9 @@ const Style = styled.div<{ active: boolean }>` border-radius: ${CSSVariable.BORDER_RADIUS_NORMAL}; > .label { + flex: 1; + min-width: 0; + font-size: ${CSSVariable.TEXT_SIZE_NORMAL}; ${capitalize} } @@ -46,16 +49,19 @@ function MenuItem({ active = false, icon, label, + suffix, ...props }: HtmlHTMLAttributes & { active?: boolean; icon: ReactNode; label: string; + suffix?: ReactNode; }) { return ( ); } diff --git a/apps/pwa/src/constants/browser.ts b/apps/pwa/src/constants/browser.ts index 9ea7d74c..5ac70ffe 100644 --- a/apps/pwa/src/constants/browser.ts +++ b/apps/pwa/src/constants/browser.ts @@ -3,11 +3,5 @@ export const IS_TOUCHABLE = export const IS_MAC_OS = window.navigator.userAgent.toLowerCase().includes('mac os') && !IS_TOUCHABLE; -export const IS_WINDOWS = window.navigator.userAgent - .toLowerCase() - .includes('windows'); -export const IS_IPAD = - window.navigator.userAgent.toLowerCase().includes('mac os') && IS_TOUCHABLE; -export const IS_IPHONE = window.navigator.userAgent - .toLowerCase() - .includes('iphone'); + +export const ENABLE_FILE_SYSTEM = 'showDirectoryPicker' in window; diff --git a/apps/pwa/src/constants/index.ts b/apps/pwa/src/constants/index.ts index 929ce029..2b3065f2 100644 --- a/apps/pwa/src/constants/index.ts +++ b/apps/pwa/src/constants/index.ts @@ -18,3 +18,8 @@ export enum Query { export const MINI_MODE_MAX_WIDTH = 720; export const NORMAL_REQUEST_MINIMAL_DURATION = 500; + +export interface Position { + x: number; + y: number; +} diff --git a/apps/pwa/src/constants/route.ts b/apps/pwa/src/constants/route.ts index c9d7c519..f687b994 100644 --- a/apps/pwa/src/constants/route.ts +++ b/apps/pwa/src/constants/route.ts @@ -13,4 +13,5 @@ export const PLAYER_PATH = { USER_MANAGE: '/user_manage', PUBLIC_MUSICBILL_COLLECTION: '/public_musicbill_collection', MUSIC_PLAY_RECORD: '/music_play_record', + DOWNLOADING_MUSIC: '/downloading_music', }; diff --git a/apps/pwa/src/i18n/en.ts b/apps/pwa/src/i18n/en.ts index 274821c8..823309f8 100644 --- a/apps/pwa/src/i18n/en.ts +++ b/apps/pwa/src/i18n/en.ts @@ -232,4 +232,9 @@ export default { timeout_while_fetching_data: 'timeout while fetching data', timeout: 'timeout of %s1 milliseconds', question_restore_playlist: 'do you want to restore last playlist ?', + multiple_singers: 'multiple singers', + clean_successful_items: 'clean successful items', + retry_failed_items: 'retry failed items', + clean_all_items: 'clean all items', + download: 'download', }; diff --git a/apps/pwa/src/i18n/zh_hans.ts b/apps/pwa/src/i18n/zh_hans.ts index 22334b2f..05473700 100644 --- a/apps/pwa/src/i18n/zh_hans.ts +++ b/apps/pwa/src/i18n/zh_hans.ts @@ -220,6 +220,11 @@ const zhCN: { timeout_while_fetching_data: '获取数据超时', timeout: '超时 %s1 毫秒', question_restore_playlist: '是否恢复上一次的播放列表', + multiple_singers: '群星', + clean_successful_items: '清理已完成项目', + retry_failed_items: '重试失败项目', + clean_all_items: '清理所有项目', + download: '下载', }; export default zhCN; diff --git a/apps/pwa/src/pages/player/components/music_base.tsx b/apps/pwa/src/pages/player/components/music_base.tsx index 8912a860..694ce503 100644 --- a/apps/pwa/src/pages/player/components/music_base.tsx +++ b/apps/pwa/src/pages/player/components/music_base.tsx @@ -85,14 +85,14 @@ const Style = styled.div<{ active: boolean }>` `; function MusicBase({ - active, + active = false, index, music, lineAfter, addon, ...props }: HtmlHTMLAttributes & { - active: boolean; + active?: boolean; index: number; music: { id: string; diff --git a/apps/pwa/src/pages/player/constants.ts b/apps/pwa/src/pages/player/constants.ts index ef3e2c10..9bb0f1c8 100644 --- a/apps/pwa/src/pages/player/constants.ts +++ b/apps/pwa/src/pages/player/constants.ts @@ -73,7 +73,7 @@ export const ZIndex = { POPUP: UtilZIndex.PAGINATION - 2, DIALOG: UtilZIndex.PAGINATION - 2, - STOP_TIMER: UtilZIndex.PAGINATION - 1, + FLOATING: UtilZIndex.PAGINATION - 1, }; export enum SearchTab { @@ -91,3 +91,17 @@ export interface StopTimerPosition { direction: 'left' | 'right'; top: number; } + +export enum DownloadStatus { + WAITING, + DOWNLOADING, + FAILED, + SUCCESSFUL, +} + +export interface DownloadingMusic { + id: string; + music: Music; + directoryHandle: FileSystemDirectoryHandle; + status: DownloadStatus; +} diff --git a/apps/pwa/src/pages/player/context.ts b/apps/pwa/src/pages/player/context.ts index 6be3e13e..d3821878 100644 --- a/apps/pwa/src/pages/player/context.ts +++ b/apps/pwa/src/pages/player/context.ts @@ -5,6 +5,7 @@ import { QueueMusic, Musicbill, StopTimer, + DownloadingMusic, } from './constants'; interface Context { @@ -24,6 +25,8 @@ interface Context { lyricPanelOpen: boolean; stopTimer: StopTimer | null; + + downloadingMusicList: DownloadingMusic[]; } const context = createContext({ @@ -43,6 +46,8 @@ const context = createContext({ lyricPanelOpen: false, stopTimer: null, + + downloadingMusicList: [], }); export default context; diff --git a/apps/pwa/src/pages/player/eventemitter.ts b/apps/pwa/src/pages/player/eventemitter.ts index 55113de9..8b7cfac9 100644 --- a/apps/pwa/src/pages/player/eventemitter.ts +++ b/apps/pwa/src/pages/player/eventemitter.ts @@ -1,7 +1,17 @@ import Eventin from 'eventin'; -import { Musicbill, MusicWithSingerAliases, QueueMusic } from './constants'; +import { + Music, + Musicbill, + MusicWithSingerAliases, + QueueMusic, +} from './constants'; export enum EventType { + DOWNLOAD_MUSIC_LIST = 'download-music-list', + DOWNLOAD_MUSIC_LIST_RETRY_FAILED = 'download-music-list-retry-failed', + DOWNLOAD_MUSIC_LIST_CLEAN_ALL = 'download-music-list-clean-all', + DOWNLOAD_MUSIC_LIST_REMOVE_ITEM = 'download-music-list-remove-item', + MINI_MODE_OPEN_SIDEBAR = 'mini_mode_OPEN_sidebar', MINI_MODE_CLOSE_SIDEBAR = 'mini_mode_close_sidebar', @@ -64,6 +74,14 @@ export enum EventType { export default new Eventin< EventType, { + [EventType.DOWNLOAD_MUSIC_LIST]: { + musicList: Music[]; + directoryHandle: FileSystemDirectoryHandle; + }; + [EventType.DOWNLOAD_MUSIC_LIST_RETRY_FAILED]: null; + [EventType.DOWNLOAD_MUSIC_LIST_CLEAN_ALL]: null; + [EventType.DOWNLOAD_MUSIC_LIST_REMOVE_ITEM]: { id: string }; + [EventType.MINI_MODE_OPEN_SIDEBAR]: null; [EventType.MINI_MODE_CLOSE_SIDEBAR]: null; diff --git a/apps/pwa/src/pages/player/header/use_title.ts b/apps/pwa/src/pages/player/header/use_title.ts index 467dcc8b..d00d3cac 100644 --- a/apps/pwa/src/pages/player/header/use_title.ts +++ b/apps/pwa/src/pages/player/header/use_title.ts @@ -40,6 +40,10 @@ export default () => { title = t('music_play_record_short'); break; } + case ROOT_PATH.PLAYER + PLAYER_PATH.DOWNLOADING_MUSIC: { + title = t('download'); + break; + } default: { title = t('cicada'); } diff --git a/apps/pwa/src/pages/player/index.tsx b/apps/pwa/src/pages/player/index.tsx index e6b97486..0e205ca7 100644 --- a/apps/pwa/src/pages/player/index.tsx +++ b/apps/pwa/src/pages/player/index.tsx @@ -12,7 +12,7 @@ import Route from './route'; import useMusicbillList from './use_musicbill_list'; import usePlaylist from './use_playlist'; import usePlayqueue from './use_playqueue'; -import Context from './context'; +import context from './context'; import useAudio from './use_audio'; import useMediaSession from './use_media_session'; import MusicDrawer from './music_drawer'; @@ -35,6 +35,7 @@ import useProfileUpdate from './use_profile_update'; import TwoFADialog from './2fa_dialog'; import useStopTimer from './use_stop_timer'; import StopTimer from './stop_timer'; +import useDownload from './use_download'; const Style = styled(PageContainer)` display: flex; @@ -89,6 +90,7 @@ function Wrapper() { [queueMusic], ); + const downloadingMusicList = useDownload(); const contextValue = useMemo( () => ({ getMusicbillListStatus, @@ -107,6 +109,8 @@ function Wrapper() { lyricPanelOpen, stopTimer, + + downloadingMusicList, }), [ audioBufferedPercent, @@ -120,10 +124,11 @@ function Wrapper() { playlist, playqueue, stopTimer, + downloadingMusicList, ], ); return ( - + + ); +} + +export default DownloadingMusic; diff --git a/apps/pwa/src/pages/player/pages/downloading_music/music_list/index.tsx b/apps/pwa/src/pages/player/pages/downloading_music/music_list/index.tsx new file mode 100644 index 00000000..58e8b876 --- /dev/null +++ b/apps/pwa/src/pages/player/pages/downloading_music/music_list/index.tsx @@ -0,0 +1,146 @@ +import styled from 'styled-components'; +import { + DownloadingMusic, + DownloadStatus as DownloadStatusType, + HEADER_HEIGHT, +} from '../../../constants'; +import List from 'react-list'; +import MusicBase from '../../../components/music_base'; +import { + MdAccessTime, + MdDownloadDone, + MdOutlineWarningAmber, + MdClose, +} from 'react-icons/md'; +import { CSSProperties, useContext } from 'react'; +import { CSSVariable } from '@/global_style'; +import Spinner from '@/components/spinner'; +import autoScrollbar from '@/style/auto_scrollbar'; +import { TOOLBAR_HEIGHT } from '../constants'; +import context from '@/pages/player/context'; +import Empty from '@/components/empty'; +import absoluteFullSize from '@/style/absolute_full_size'; +import IconButton from '@/components/icon_button'; +import eventemitter, { EventType } from '@/pages/player/eventemitter'; +import dialog from '@/utils/dialog'; + +const LineAfter = styled.div` + display: flex; + align-items: center; + gap: 5px; +`; +const Style = styled.div` + flex: 1; + min-height: 0; + + position: relative; + padding-block: ${HEADER_HEIGHT}px ${TOOLBAR_HEIGHT}px; + + ${autoScrollbar} + overflow: auto; +`; +const StyledEmpty = styled(Empty)` + ${absoluteFullSize} +`; +const DOWNLOAD_STATUS_SIZE = 24; +const downloadStatusStyle: CSSProperties = { + fontSize: DOWNLOAD_STATUS_SIZE, +}; +const successfulStyle: CSSProperties = { + ...downloadStatusStyle, + color: CSSVariable.COLOR_PRIMARY, +}; +const waitingStyle: CSSProperties = { + ...downloadStatusStyle, + color: CSSVariable.TEXT_COLOR_SECONDARY, +}; +const failedStyle: CSSProperties = { + ...downloadStatusStyle, + color: CSSVariable.COLOR_DANGEROUS, +}; +const removeStyle: CSSProperties = { + color: CSSVariable.COLOR_DANGEROUS, +}; + +function DownloadStatus({ + downloadingMusic, +}: { + downloadingMusic: DownloadingMusic; +}) { + const { status } = downloadingMusic; + switch (status) { + case DownloadStatusType.DOWNLOADING: { + return ; + } + case DownloadStatusType.WAITING: { + return ; + } + case DownloadStatusType.SUCCESSFUL: { + return ; + } + case DownloadStatusType.FAILED: { + return ; + } + default: { + return null; + } + } +} + +function MusicList() { + const { downloadingMusicList } = useContext(context); + const length = downloadingMusicList.length; + return ( + + ); +} + +export default MusicList; diff --git a/apps/pwa/src/pages/player/pages/downloading_music/toolbar/index.tsx b/apps/pwa/src/pages/player/pages/downloading_music/toolbar/index.tsx new file mode 100644 index 00000000..d286f2f8 --- /dev/null +++ b/apps/pwa/src/pages/player/pages/downloading_music/toolbar/index.tsx @@ -0,0 +1,59 @@ +import styled from 'styled-components'; +import { TOOLBAR_HEIGHT } from '../constants'; +import IconButton from '@/components/icon_button'; +import { MdPlaylistRemove, MdOutlineRestartAlt } from 'react-icons/md'; +import { useContext, useMemo } from 'react'; +import context from '@/pages/player/context'; +import dialog from '@/utils/dialog'; +import eventemitter, { EventType } from '@/pages/player/eventemitter'; +import { DownloadStatus } from '@/pages/player/constants'; + +const Style = styled.div` + position: absolute; + width: 100%; + height: ${TOOLBAR_HEIGHT}px; + left: 0; + bottom: 0; + + padding: 0 20px; + + display: flex; + align-items: center; + gap: 10px; + + backdrop-filter: blur(5px); +`; + +function Toolbar() { + const { downloadingMusicList } = useContext(context); + const failed = useMemo( + () => downloadingMusicList.find((m) => m.status === DownloadStatus.FAILED), + [downloadingMusicList], + ); + return ( + + ); +} + +export default Toolbar; diff --git a/apps/pwa/src/pages/player/pages/musicbill/operation.tsx b/apps/pwa/src/pages/player/pages/musicbill/operation.tsx index 24ca7bc1..a0c3591d 100644 --- a/apps/pwa/src/pages/player/pages/musicbill/operation.tsx +++ b/apps/pwa/src/pages/player/pages/musicbill/operation.tsx @@ -5,6 +5,7 @@ import { MdPlaylistAdd, MdOutlineEdit, MdOutlinePeopleAlt, + MdOutlineDownload, } from 'react-icons/md'; import { RequestStatus } from '@/constants'; import notice from '@/utils/notice'; @@ -15,6 +16,8 @@ import playerEventemitter, { } from '../../eventemitter'; import { Musicbill } from '../../constants'; import e, { EventType } from './eventemitter'; +import { ENABLE_FILE_SYSTEM } from '@/constants/browser'; +import { downloadMusicListByFileSystem } from '../../utils'; const Style = styled.div` display: flex; @@ -56,6 +59,14 @@ function Operation({ musicbill }: { musicbill: Musicbill }) { e.emit(EventType.OPEN_EDIT_MENU, null)}> + {ENABLE_FILE_SYSTEM ? ( + downloadMusicListByFileSystem(musicbill.musicList)} + > + + + ) : null} playerEventemitter.emit( diff --git a/apps/pwa/src/pages/player/playlist_playqueue_drawer/playlist/toolbar.tsx b/apps/pwa/src/pages/player/playlist_playqueue_drawer/playlist/toolbar.tsx index b7eb6d40..d382adf0 100644 --- a/apps/pwa/src/pages/player/playlist_playqueue_drawer/playlist/toolbar.tsx +++ b/apps/pwa/src/pages/player/playlist_playqueue_drawer/playlist/toolbar.tsx @@ -1,9 +1,9 @@ import Label from '@/components/label'; import Input from '@/components/input'; -import { useEffect, useState } from 'react'; +import { useContext, useEffect, useState } from 'react'; import styled from 'styled-components'; import IconButton from '@/components/icon_button'; -import { MdDelete } from 'react-icons/md'; +import { MdPlaylistRemove } from 'react-icons/md'; import dialog from '@/utils/dialog'; import { t } from '@/i18n'; import { FILTER_HEIGHT } from './constants'; @@ -11,6 +11,7 @@ import playerEventemitter, { EventType as PlayerEventType, } from '../../eventemitter'; import capitalize from '#/utils/capitalize'; +import context from '../../context'; const Style = styled.div` position: absolute; @@ -45,9 +46,11 @@ function Toolbar({ return () => window.clearTimeout(timer); }, [keyword, onKeywordChange]); + const { playlist } = useContext(context); return ( + ); +} + +export default DownloadTag; diff --git a/apps/pwa/src/pages/player/sidebar/menu.tsx b/apps/pwa/src/pages/player/sidebar/menu.tsx index fdf3dfe0..f2c43157 100644 --- a/apps/pwa/src/pages/player/sidebar/menu.tsx +++ b/apps/pwa/src/pages/player/sidebar/menu.tsx @@ -4,11 +4,15 @@ import { MdOutlineSettings, MdOutlineMusicNote, MdHistory, + MdOutlineDownload, } from 'react-icons/md'; import MenuItem from '@/components/menu_item'; import { useLocation, useNavigate } from 'react-router-dom'; -import { CSSProperties } from 'react'; +import { CSSProperties, useContext } from 'react'; import { t } from '@/i18n'; +import context from '../context'; +import { ENABLE_FILE_SYSTEM } from '@/constants/browser'; +import DownloadTag from './download_tag'; const itemStyle: CSSProperties = { margin: '0 10px' }; @@ -16,6 +20,7 @@ function Menu() { const { pathname } = useLocation(); const navigate = useNavigate(); + const { downloadingMusicList } = useContext(context); return (
} /> + {ENABLE_FILE_SYSTEM && downloadingMusicList.length ? ( + + navigate(`${ROOT_PATH.PLAYER}${PLAYER_PATH.DOWNLOADING_MUSIC}`) + } + label={t('download')} + icon={} + suffix={} + /> + ) : null}
); } diff --git a/apps/pwa/src/pages/player/stop_timer/index.tsx b/apps/pwa/src/pages/player/stop_timer/index.tsx index 4acdd7d7..e7a38e96 100644 --- a/apps/pwa/src/pages/player/stop_timer/index.tsx +++ b/apps/pwa/src/pages/player/stop_timer/index.tsx @@ -9,7 +9,7 @@ import Content from './content'; import useTitlebarArea from '@/utils/use_titlebar_area_rect'; const Style = styled.div` - z-index: ${ZIndex.STOP_TIMER}; + z-index: ${ZIndex.FLOATING}; position: absolute; cursor: grab; diff --git a/apps/pwa/src/pages/player/use_download.ts b/apps/pwa/src/pages/player/use_download.ts new file mode 100644 index 00000000..36a9c89f --- /dev/null +++ b/apps/pwa/src/pages/player/use_download.ts @@ -0,0 +1,144 @@ +import { useEffect, useState } from 'react'; +import { DownloadingMusic, DownloadStatus } from './constants'; +import eventemitter, { EventType } from './eventemitter'; +import generateRandomString from '#/utils/generate_random_string'; +import formatMusicFilename from '#/utils/format_music_filename'; +import logger from '@/utils/logger'; +import timeout from '#/utils/timeout'; +import useNavigate from '@/utils/use_navigate'; +import { PLAYER_PATH, ROOT_PATH } from '@/constants/route'; + +async function downloadAndSave(downloadingMusic: DownloadingMusic) { + const { music, directoryHandle } = downloadingMusic; + const fileHandle = await directoryHandle.getFileHandle( + formatMusicFilename({ + name: music.name, + singerNames: music.singers.map((s) => s.name), + ext: music.asset.split('.').at(-1)!, + }), + { + create: true, + }, + ); + const writable = await fileHandle.createWritable(); + const response = await globalThis.fetch(music.asset); + await response.body?.pipeTo(writable); +} + +function downloadAndSaveWithTimeout(downloadingMusic: DownloadingMusic) { + return Promise.race([ + downloadAndSave(downloadingMusic), + timeout(1000 * 60 * 5), + ]); +} + +function useDownload() { + const navigate = useNavigate(); + const [downloadingMusicList, setDownloadingMusicList] = useState< + DownloadingMusic[] + >([]); + + useEffect( + () => + eventemitter.listen(EventType.DOWNLOAD_MUSIC_LIST_CLEAN_ALL, () => + setDownloadingMusicList([]), + ), + [], + ); + + useEffect( + () => + eventemitter.listen( + EventType.DOWNLOAD_MUSIC_LIST_REMOVE_ITEM, + (payload) => + setDownloadingMusicList((dml) => + dml.filter((m) => m.id !== payload.id), + ), + ), + [], + ); + + useEffect( + () => + eventemitter.listen(EventType.DOWNLOAD_MUSIC_LIST, (payload) => { + setDownloadingMusicList((dml) => [ + ...payload.musicList.map( + (music) => + ({ + id: generateRandomString(), + music, + directoryHandle: payload.directoryHandle, + status: DownloadStatus.WAITING, + } satisfies DownloadingMusic), + ), + ...dml, + ]); + globalThis.setTimeout(() => + navigate({ path: ROOT_PATH.PLAYER + PLAYER_PATH.DOWNLOADING_MUSIC }), + ); + }), + [navigate], + ); + + useEffect( + () => + eventemitter.listen(EventType.DOWNLOAD_MUSIC_LIST_RETRY_FAILED, () => + setDownloadingMusicList((dml) => + dml.map((m) => + m.status === DownloadStatus.FAILED + ? { + ...m, + status: DownloadStatus.WAITING, + } + : m, + ), + ), + ), + [], + ); + + useEffect(() => { + const downloadingList = downloadingMusicList.filter( + (m) => m.status === DownloadStatus.DOWNLOADING, + ); + if (downloadingList.length >= 3) { + return; + } + const waiting = downloadingMusicList.findLast( + (m) => m.status === DownloadStatus.WAITING, + ); + if (waiting) { + setDownloadingMusicList((ml) => + ml.map((m) => + m.id === waiting.id + ? { ...waiting, status: DownloadStatus.DOWNLOADING } + : m, + ), + ); + downloadAndSaveWithTimeout(waiting) + .then(() => + setDownloadingMusicList((ml) => + ml.map((m) => + m.id === waiting.id + ? { ...waiting, status: DownloadStatus.SUCCESSFUL } + : m, + ), + ), + ) + .catch((error) => { + logger.error(error, '下载并保存音乐失败'); + setDownloadingMusicList((ml) => + ml.map((m) => + m.id === waiting.id + ? { ...waiting, status: DownloadStatus.FAILED } + : m, + ), + ); + }); + } + }, [downloadingMusicList]); + + return downloadingMusicList; +} + +export default useDownload; diff --git a/apps/pwa/src/pages/player/utils.ts b/apps/pwa/src/pages/player/utils.ts index 247b9bb9..a4a0f4dd 100644 --- a/apps/pwa/src/pages/player/utils.ts +++ b/apps/pwa/src/pages/player/utils.ts @@ -79,12 +79,12 @@ export function openCreateSingerDialog(callback: (id: string) => void) { }); } -export const filterMusic = ( +export function filterMusic( music: Omit & { singers: SingerWithAliases[]; }, keyword: string, -) => { +) { if (keyword) { const lowerCaseKeyword = keyword.toLowerCase(); return ( @@ -100,12 +100,31 @@ export const filterMusic = ( ); } return true; -}; +} -export const formatSecond = (s: number) => { +export function formatSecond(s: number) { const minute = Math.floor(s / 60); const second = Math.floor(s % 60); return `${minute < 10 ? '0' : ''}${minute}:${ second < 10 ? '0' : '' }${second}`; -}; +} + +export async function downloadMusicListByFileSystem(musicList: Music[]) { + try { + const directoryHandle = await window.showDirectoryPicker({ + mode: 'readwrite', + startIn: 'downloads', + }); + e.emit(EventType.DOWNLOAD_MUSIC_LIST, { + musicList, + directoryHandle, + }); + } catch (error) { + if (error instanceof DOMException && error.name === 'AbortError') { + return; + } + logger.error(error, '无法选择保存目录'); + notice.error(error.message); + } +} diff --git a/apps/pwa/src/platform/with_login.tsx b/apps/pwa/src/platform/with_login.tsx index 52213d11..d238459f 100644 --- a/apps/pwa/src/platform/with_login.tsx +++ b/apps/pwa/src/platform/with_login.tsx @@ -4,12 +4,12 @@ import { ROOT_PATH } from '@/constants/route'; import { ComponentType } from 'react'; import { Query } from '@/constants'; -function withLogin(Component: ComponentType) { +function withLogin(Component: ComponentType) { return function ComponentWithUser(props: Props) { const user = useUser(); const { pathname, search } = useLocation(); return user ? ( - // @ts-expect-error + // @ts-expect-error: known issue ) : ( ('app'); diff --git a/apps/pwa/src/updater.tsx b/apps/pwa/src/updater.tsx index 925599e0..871bd39d 100644 --- a/apps/pwa/src/updater.tsx +++ b/apps/pwa/src/updater.tsx @@ -69,15 +69,6 @@ if ('serviceWorker' in navigator) { { duration: 0, closable: false }, ); }); - - /** - * 定时检查更新 - * @author mebtte - */ - window.setInterval(() => { - notice.close(updateNoticeId); - return wb.update(); - }, 1000 * 60 * 60); } }); } else { diff --git a/apps/pwa/tsconfig.json b/apps/pwa/tsconfig.json index 804a14f9..f522c974 100644 --- a/apps/pwa/tsconfig.json +++ b/apps/pwa/tsconfig.json @@ -4,7 +4,7 @@ "compilerOptions": { "module": "ESNext", "jsx": "preserve", // 由 babel 进一步编译 - "lib": ["DOM", "WebWorker", "ESNext"], + "lib": ["DOM", "WebWorker", "ESNext", "DOM.Iterable"], "baseUrl": ".", "paths": { diff --git a/package-lock.json b/package-lock.json index e3f35b3a..a4c011e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -70,6 +70,7 @@ "@types/resize-observer-browser": "^0.1.5", "@types/sqlite3": "^3.1.8", "@types/styled-components": "^5.1.25", + "@types/wicg-file-system-access": "^2023.10.7", "@typescript-eslint/eslint-plugin": "^6.19.0", "array-move": "^4.0.0", "babel-jest": "^29.7.0", @@ -134,7 +135,7 @@ "styled-components": "^5.3.5", "ts-node": "^10.9.1", "tsconfig-paths": "^4.0.0", - "typescript": "^5.3.3", + "typescript": "^5.9.3", "webpack": "^5.84.1", "webpack-cli": "^5.1.1", "webpack-dev-server": "^4.15.0", @@ -215,6 +216,7 @@ "version": "7.22.15", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.22.13", @@ -627,7 +629,6 @@ "version": "7.20.7", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-environment-visitor": "^7.18.9", "@babel/helper-plugin-utils": "^7.20.2", @@ -660,7 +661,6 @@ "version": "7.22.5", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-export-default-from": "^7.22.5" @@ -676,7 +676,6 @@ "version": "7.18.6", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" @@ -692,7 +691,6 @@ "version": "7.18.6", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-numeric-separator": "^7.10.4" @@ -708,7 +706,6 @@ "version": "7.20.7", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/compat-data": "^7.20.5", "@babel/helper-compilation-targets": "^7.20.7", @@ -727,7 +724,6 @@ "version": "7.18.6", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" @@ -743,7 +739,6 @@ "version": "7.21.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.20.2", "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", @@ -830,7 +825,6 @@ "version": "7.22.5", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, @@ -856,7 +850,6 @@ "version": "7.22.5", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, @@ -1292,7 +1285,6 @@ "version": "7.22.5", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-flow": "^7.22.5" @@ -1692,7 +1684,6 @@ "version": "7.22.5", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, @@ -1707,7 +1698,6 @@ "version": "7.22.5", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, @@ -1933,6 +1923,7 @@ "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.2.tgz", "integrity": "sha512-BW3gsuDD+rvHL2VO2SjAUNTBe5YrjsTiDyqamPDWY723na3/yPQ65X5oQkFVJZ0o50/2d+svm1rkPoJeR1KxVQ==", "dev": true, + "peer": true, "dependencies": { "@babel/compat-data": "^7.23.2", "@babel/helper-compilation-targets": "^7.22.15", @@ -2026,7 +2017,6 @@ "version": "7.22.15", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-validator-option": "^7.22.15", @@ -2095,7 +2085,6 @@ "version": "7.22.15", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "clone-deep": "^4.0.1", "find-cache-dir": "^2.0.0", @@ -2114,7 +2103,6 @@ "version": "2.1.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "commondir": "^1.0.1", "make-dir": "^2.0.0", @@ -2128,7 +2116,6 @@ "version": "3.0.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "locate-path": "^3.0.0" }, @@ -2140,7 +2127,6 @@ "version": "3.0.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" @@ -2153,7 +2139,6 @@ "version": "2.1.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "pify": "^4.0.1", "semver": "^5.6.0" @@ -2166,7 +2151,6 @@ "version": "2.3.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-try": "^2.0.0" }, @@ -2181,7 +2165,6 @@ "version": "3.0.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-limit": "^2.0.0" }, @@ -2193,7 +2176,6 @@ "version": "3.0.0", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -2202,7 +2184,6 @@ "version": "3.0.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "find-up": "^3.0.0" }, @@ -2214,7 +2195,6 @@ "version": "5.7.2", "dev": true, "license": "ISC", - "peer": true, "bin": { "semver": "bin/semver" } @@ -2223,7 +2203,6 @@ "version": "0.5.21", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -2572,14 +2551,12 @@ "node_modules/@hapi/hoek": { "version": "9.3.0", "dev": true, - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/@hapi/topo": { "version": "5.1.0", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "@hapi/hoek": "^9.0.0" } @@ -2792,7 +2769,6 @@ "version": "29.6.3", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/types": "^29.6.3" }, @@ -3048,6 +3024,7 @@ "node_modules/@jimp/custom": { "version": "0.22.10", "license": "MIT", + "peer": true, "dependencies": { "@jimp/core": "^0.22.10" } @@ -3078,6 +3055,7 @@ "node_modules/@jimp/plugin-blit": { "version": "0.22.10", "license": "MIT", + "peer": true, "dependencies": { "@jimp/utils": "^0.22.10" }, @@ -3088,6 +3066,7 @@ "node_modules/@jimp/plugin-blur": { "version": "0.22.10", "license": "MIT", + "peer": true, "dependencies": { "@jimp/utils": "^0.22.10" }, @@ -3108,6 +3087,7 @@ "node_modules/@jimp/plugin-color": { "version": "0.22.10", "license": "MIT", + "peer": true, "dependencies": { "@jimp/utils": "^0.22.10", "tinycolor2": "^1.6.0" @@ -3145,6 +3125,7 @@ "node_modules/@jimp/plugin-crop": { "version": "0.22.10", "license": "MIT", + "peer": true, "dependencies": { "@jimp/utils": "^0.22.10" }, @@ -3248,6 +3229,7 @@ "node_modules/@jimp/plugin-resize": { "version": "0.22.10", "license": "MIT", + "peer": true, "dependencies": { "@jimp/utils": "^0.22.10" }, @@ -3258,6 +3240,7 @@ "node_modules/@jimp/plugin-rotate": { "version": "0.22.10", "license": "MIT", + "peer": true, "dependencies": { "@jimp/utils": "^0.22.10" }, @@ -3271,6 +3254,7 @@ "node_modules/@jimp/plugin-scale": { "version": "0.22.10", "license": "MIT", + "peer": true, "dependencies": { "@jimp/utils": "^0.22.10" }, @@ -3591,7 +3575,6 @@ "version": "11.3.6", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@react-native-community/cli-clean": "11.3.6", "@react-native-community/cli-config": "11.3.6", @@ -3622,7 +3605,6 @@ "version": "11.3.6", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@react-native-community/cli-tools": "11.3.6", "chalk": "^4.1.2", @@ -3634,7 +3616,6 @@ "version": "11.3.6", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@react-native-community/cli-tools": "11.3.6", "chalk": "^4.1.2", @@ -3648,7 +3629,6 @@ "version": "1.0.10", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "sprintf-js": "~1.0.2" } @@ -3657,7 +3637,6 @@ "version": "5.2.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "import-fresh": "^2.0.0", "is-directory": "^0.3.1", @@ -3672,7 +3651,6 @@ "version": "2.0.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "caller-path": "^2.0.0", "resolve-from": "^3.0.0" @@ -3685,7 +3663,6 @@ "version": "3.14.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -3698,7 +3675,6 @@ "version": "4.0.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" @@ -3711,7 +3687,6 @@ "version": "3.0.0", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -3720,7 +3695,6 @@ "version": "11.3.6", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "serve-static": "^1.13.1" } @@ -3729,7 +3703,6 @@ "version": "11.3.6", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@react-native-community/cli-config": "11.3.6", "@react-native-community/cli-platform-android": "11.3.6", @@ -3755,7 +3728,6 @@ "version": "4.1.1", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -3764,7 +3736,6 @@ "version": "6.0.0", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "yallist": "^4.0.0" }, @@ -3776,7 +3747,6 @@ "version": "7.5.4", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -3791,7 +3761,6 @@ "version": "5.2.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^4.1.0" }, @@ -3802,14 +3771,12 @@ "node_modules/@react-native-community/cli-doctor/node_modules/yallist": { "version": "4.0.0", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/@react-native-community/cli-doctor/node_modules/yaml": { "version": "2.3.2", "dev": true, "license": "ISC", - "peer": true, "engines": { "node": ">= 14" } @@ -3818,7 +3785,6 @@ "version": "11.3.6", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@react-native-community/cli-platform-android": "11.3.6", "@react-native-community/cli-tools": "11.3.6", @@ -3831,7 +3797,6 @@ "version": "11.3.6", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@react-native-community/cli-tools": "11.3.6", "chalk": "^4.1.2", @@ -3844,7 +3809,6 @@ "version": "11.3.6", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@react-native-community/cli-tools": "11.3.6", "chalk": "^4.1.2", @@ -3858,7 +3822,6 @@ "version": "11.3.6", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@react-native-community/cli-server-api": "11.3.6", "@react-native-community/cli-tools": "11.3.6", @@ -3877,7 +3840,6 @@ "version": "0.76.7", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.0.0", "react-refresh": "^0.4.0" @@ -3890,7 +3852,6 @@ "version": "11.3.6", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@react-native-community/cli-debugger-ui": "11.3.6", "@react-native-community/cli-tools": "11.3.6", @@ -3907,7 +3868,6 @@ "version": "26.6.2", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", @@ -3923,7 +3883,6 @@ "version": "15.0.15", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/yargs-parser": "*" } @@ -3932,7 +3891,6 @@ "version": "4.3.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -3947,7 +3905,6 @@ "version": "2.0.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "color-name": "~1.1.4" }, @@ -3958,14 +3915,12 @@ "node_modules/@react-native-community/cli-server-api/node_modules/color-name": { "version": "1.1.4", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@react-native-community/cli-server-api/node_modules/pretty-format": { "version": "26.6.2", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/types": "^26.6.2", "ansi-regex": "^5.0.0", @@ -3979,14 +3934,12 @@ "node_modules/@react-native-community/cli-server-api/node_modules/react-is": { "version": "17.0.2", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@react-native-community/cli-server-api/node_modules/ws": { "version": "7.5.9", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8.3.0" }, @@ -4007,7 +3960,6 @@ "version": "11.3.6", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "appdirsjs": "^1.2.4", "chalk": "^4.1.2", @@ -4024,7 +3976,6 @@ "version": "6.0.0", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "yallist": "^4.0.0" }, @@ -4036,7 +3987,6 @@ "version": "2.6.0", "dev": true, "license": "MIT", - "peer": true, "bin": { "mime": "cli.js" }, @@ -4048,7 +3998,6 @@ "version": "7.5.4", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -4062,14 +4011,12 @@ "node_modules/@react-native-community/cli-tools/node_modules/yallist": { "version": "4.0.0", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/@react-native-community/cli-types": { "version": "11.3.6", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "joi": "^17.2.1" } @@ -4078,7 +4025,6 @@ "version": "4.1.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -4091,7 +4037,6 @@ "version": "8.1.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", @@ -4105,7 +4050,6 @@ "version": "4.0.0", "dev": true, "license": "MIT", - "peer": true, "optionalDependencies": { "graceful-fs": "^4.1.6" } @@ -4114,7 +4058,6 @@ "version": "5.0.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-locate": "^4.1.0" }, @@ -4126,7 +4069,6 @@ "version": "6.0.0", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "yallist": "^4.0.0" }, @@ -4138,7 +4080,6 @@ "version": "2.3.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-try": "^2.0.0" }, @@ -4153,7 +4094,6 @@ "version": "4.1.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-limit": "^2.2.0" }, @@ -4165,7 +4105,6 @@ "version": "7.5.4", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -4180,7 +4119,6 @@ "version": "0.1.2", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 4.0.0" } @@ -4188,20 +4126,17 @@ "node_modules/@react-native-community/cli/node_modules/yallist": { "version": "4.0.0", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/@react-native/assets-registry": { "version": "0.72.0", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@react-native/codegen": { "version": "0.72.6", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/parser": "^7.20.0", "flow-parser": "^0.206.0", @@ -4215,26 +4150,22 @@ "node_modules/@react-native/gradle-plugin": { "version": "0.72.11", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@react-native/js-polyfills": { "version": "0.72.1", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@react-native/normalize-colors": { "version": "0.72.0", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@react-native/virtualized-lists": { "version": "0.72.8", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" @@ -4371,7 +4302,6 @@ "version": "8.14.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.17.8", "@types/react-reconciler": "^0.26.7", @@ -4418,7 +4348,6 @@ "version": "0.21.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" } @@ -4428,7 +4357,6 @@ "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz", "integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==", "dev": true, - "peer": true, "engines": { "node": ">=12.7.0" }, @@ -4527,7 +4455,6 @@ "version": "4.1.4", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "@hapi/hoek": "^9.0.0" } @@ -4535,14 +4462,12 @@ "node_modules/@sideway/formula": { "version": "3.0.1", "dev": true, - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/@sideway/pinpoint": { "version": "2.0.0", "dev": true, - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/@sinclair/typebox": { "version": "0.27.8", @@ -5101,6 +5026,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.71.tgz", "integrity": "sha512-evXpcgtZm8FY4jqBSN8+DmOTcVkkvTmAayeo4Wf3m1xAruyVGzGuDh/Fb/WWX2yLItUiho42ozyJjB0dw//Tkw==", "dev": true, + "peer": true, "dependencies": { "undici-types": "~5.26.4" } @@ -5171,7 +5097,6 @@ "version": "0.26.7", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/react": "*" } @@ -5297,6 +5222,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/wicg-file-system-access": { + "version": "2023.10.7", + "resolved": "https://registry.npmjs.org/@types/wicg-file-system-access/-/wicg-file-system-access-2023.10.7.tgz", + "integrity": "sha512-g49ijasEJvCd7ifmAY2D0wdEtt1xRjBbA33PJTiv8mKBr7DoMsPeISoJ8oQOTopSRi+FBWPpPW5ouDj2QPKtGA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/ws": { "version": "8.5.5", "dev": true, @@ -5323,6 +5255,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.19.0.tgz", "integrity": "sha512-DUCUkQNklCQYnrBSSikjVChdc84/vMPDQSgJTHBZ64G9bA9w0Crc0rd2diujKbTdp6w2J47qkeHQLoi0rpLCdg==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", "@typescript-eslint/scope-manager": "6.19.0", @@ -5434,6 +5367,7 @@ "version": "6.6.0", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "6.6.0", "@typescript-eslint/types": "6.6.0", @@ -6058,7 +5992,6 @@ "version": "3.0.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "event-target-shim": "^5.0.0" }, @@ -6081,6 +6014,7 @@ "version": "8.10.0", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6160,6 +6094,7 @@ "version": "6.12.6", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -6218,8 +6153,7 @@ "node_modules/anser": { "version": "1.4.10", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/ansi-escapes": { "version": "4.3.2", @@ -6238,7 +6172,6 @@ "version": "0.2.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "colorette": "^1.0.7", "slice-ansi": "^2.0.0", @@ -6249,7 +6182,6 @@ "version": "4.1.1", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -6257,14 +6189,12 @@ "node_modules/ansi-fragments/node_modules/colorette": { "version": "1.4.0", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/ansi-fragments/node_modules/is-fullwidth-code-point": { "version": "2.0.0", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -6273,7 +6203,6 @@ "version": "2.1.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-styles": "^3.2.0", "astral-regex": "^1.0.0", @@ -6287,7 +6216,6 @@ "version": "5.2.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^4.1.0" }, @@ -6343,8 +6271,7 @@ "node_modules/appdirsjs": { "version": "1.2.7", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/aproba": { "version": "2.0.0", @@ -6520,7 +6447,6 @@ "version": "0.15.2", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "tslib": "^2.0.1" }, @@ -6532,7 +6458,6 @@ "version": "1.0.0", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -6545,8 +6470,7 @@ "node_modules/async-limiter": { "version": "1.0.1", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/asynciterator.prototype": { "version": "1.0.0", @@ -6585,7 +6509,6 @@ "version": "7.0.0-bridge.0", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { "@babel/core": "^7.0.0-0" } @@ -6680,6 +6603,7 @@ "version": "3.1.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", @@ -6748,14 +6672,12 @@ "node_modules/babel-plugin-syntax-trailing-function-commas": { "version": "7.0.0-beta.0", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/babel-plugin-transform-flow-enums": { "version": "0.0.2", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/plugin-syntax-flow": "^7.12.1" } @@ -6787,7 +6709,6 @@ "version": "3.4.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/plugin-proposal-class-properties": "^7.0.0", "@babel/plugin-proposal-object-rest-spread": "^7.0.0", @@ -7039,6 +6960,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001541", "electron-to-chromium": "^1.4.535", @@ -7242,7 +7164,6 @@ "version": "2.0.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "callsites": "^2.0.0" }, @@ -7254,7 +7175,6 @@ "version": "2.0.0", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -7263,7 +7183,6 @@ "version": "2.0.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "caller-callsite": "^2.0.0" }, @@ -7741,8 +7660,7 @@ "node_modules/command-exists": { "version": "1.2.9", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/commander": { "version": "9.5.0", @@ -7826,7 +7744,6 @@ "version": "3.7.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "debug": "2.6.9", "finalhandler": "1.1.2", @@ -7849,7 +7766,6 @@ "version": "2.6.9", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ms": "2.0.0" } @@ -7857,8 +7773,7 @@ "node_modules/connect/node_modules/ms": { "version": "2.0.0", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/console-control-strings": { "version": "1.1.0", @@ -7942,6 +7857,7 @@ "version": "8.12.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -8351,8 +8267,7 @@ "node_modules/debounce": { "version": "1.2.1", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/debug": { "version": "4.3.4", @@ -8378,7 +8293,6 @@ "version": "1.2.0", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -8502,8 +8416,7 @@ "node_modules/denodeify": { "version": "1.2.1", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/depd": { "version": "2.0.0", @@ -8516,7 +8429,6 @@ "version": "4.1.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@react-native/normalize-colors": "*", "invariant": "*", @@ -8871,7 +8783,6 @@ "version": "2.1.4", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "stackframe": "^1.3.4" } @@ -8880,7 +8791,6 @@ "version": "1.5.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "accepts": "~1.3.7", "escape-html": "~1.0.3" @@ -9069,6 +8979,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -9257,6 +9168,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", "dev": true, + "peer": true, "dependencies": { "array-includes": "^3.1.7", "array.prototype.findlastindex": "^1.2.3", @@ -9520,6 +9432,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.6.2.tgz", "integrity": "sha512-6TyDmZ1HXoFQXnhCTUjVFULReoBPOAjpuiKELMkeP40yffI/1ZRO+d9ug/VC6fqISo2WkuIBk3cvuRPALaWlOQ==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "builtins": "^5.0.1", @@ -9608,6 +9521,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==", "dev": true, + "peer": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -9829,7 +9743,6 @@ "version": "5.0.1", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -10096,7 +10009,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "strnum": "^1.0.5" }, @@ -10252,7 +10164,6 @@ "version": "1.1.2", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", @@ -10270,7 +10181,6 @@ "version": "2.6.9", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ms": "2.0.0" } @@ -10278,14 +10188,12 @@ "node_modules/finalhandler/node_modules/ms": { "version": "2.0.0", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/finalhandler/node_modules/on-finished": { "version": "2.3.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ee-first": "1.1.1" }, @@ -10350,14 +10258,12 @@ "node_modules/flow-enums-runtime": { "version": "0.0.5", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/flow-parser": { "version": "0.206.0", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.4.0" } @@ -10948,14 +10854,12 @@ "node_modules/hermes-estree": { "version": "0.12.0", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/hermes-parser": { "version": "0.12.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "hermes-estree": "0.12.0" } @@ -10964,7 +10868,6 @@ "version": "0.0.6", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "source-map": "^0.7.3" }, @@ -10976,7 +10879,6 @@ "version": "0.7.4", "dev": true, "license": "BSD-3-Clause", - "peer": true, "engines": { "node": ">= 8" } @@ -11352,7 +11254,6 @@ "version": "1.0.2", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "queue": "6.0.2" }, @@ -11523,8 +11424,7 @@ "node_modules/ip": { "version": "1.1.8", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/ipaddr.js": { "version": "2.1.0", @@ -11679,7 +11579,6 @@ "version": "0.3.1", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -12007,7 +11906,6 @@ "version": "1.1.0", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -12221,7 +12119,6 @@ "version": "1.1.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/react-reconciler": "^0.28.0" }, @@ -12233,7 +12130,6 @@ "version": "0.28.4", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/react": "*" } @@ -12260,6 +12156,7 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, + "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -12897,7 +12794,6 @@ "version": "17.10.1", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "@hapi/hoek": "^9.0.0", "@hapi/topo": "^5.0.0", @@ -12930,20 +12826,17 @@ "node_modules/jsc-android": { "version": "250231.0.0", "dev": true, - "license": "BSD-2-Clause", - "peer": true + "license": "BSD-2-Clause" }, "node_modules/jsc-safe-url": { "version": "0.2.4", "dev": true, - "license": "0BSD", - "peer": true + "license": "0BSD" }, "node_modules/jscodeshift": { "version": "0.14.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.13.16", "@babel/parser": "^7.13.16", @@ -12976,7 +12869,6 @@ "version": "2.4.3", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "graceful-fs": "^4.1.11", "imurmurhash": "^0.1.4", @@ -13136,8 +13028,7 @@ "node_modules/json-parse-better-errors": { "version": "1.0.2", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", @@ -13842,8 +13733,7 @@ "node_modules/lodash.throttle": { "version": "4.1.1", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/log-symbols": { "version": "4.1.0", @@ -13942,7 +13832,6 @@ "version": "0.7.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-fragments": "^0.2.1", "dayjs": "^1.8.15", @@ -13956,7 +13845,6 @@ "version": "6.0.0", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -13967,7 +13855,6 @@ "version": "4.1.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -13980,7 +13867,6 @@ "version": "5.0.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-locate": "^4.1.0" }, @@ -13992,7 +13878,6 @@ "version": "2.3.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-try": "^2.0.0" }, @@ -14007,7 +13892,6 @@ "version": "4.1.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-limit": "^2.2.0" }, @@ -14018,14 +13902,12 @@ "node_modules/logkitty/node_modules/y18n": { "version": "4.0.3", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/logkitty/node_modules/yargs": { "version": "15.4.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", @@ -14047,7 +13929,6 @@ "version": "18.1.3", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" @@ -14241,7 +14122,6 @@ "version": "0.76.7", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.0.0", "@babel/core": "^7.20.0", @@ -14303,7 +14183,6 @@ "version": "0.76.7", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.20.0", "hermes-parser": "0.12.0", @@ -14317,7 +14196,6 @@ "version": "0.76.7", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "metro-core": "0.76.7", "rimraf": "^3.0.2" @@ -14330,7 +14208,6 @@ "version": "0.76.7", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=16" } @@ -14339,7 +14216,6 @@ "version": "0.76.7", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "connect": "^3.6.5", "cosmiconfig": "^5.0.5", @@ -14357,7 +14233,6 @@ "version": "1.0.10", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "sprintf-js": "~1.0.2" } @@ -14366,7 +14241,6 @@ "version": "5.2.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "import-fresh": "^2.0.0", "is-directory": "^0.3.1", @@ -14381,7 +14255,6 @@ "version": "2.0.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "caller-path": "^2.0.0", "resolve-from": "^3.0.0" @@ -14394,7 +14267,6 @@ "version": "3.14.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -14407,7 +14279,6 @@ "version": "0.76.7", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.0.0", "react-refresh": "^0.4.0" @@ -14420,7 +14291,6 @@ "version": "4.0.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" @@ -14433,7 +14303,6 @@ "version": "3.0.0", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -14442,7 +14311,6 @@ "version": "0.76.7", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "lodash.throttle": "^4.1.1", "metro-resolver": "0.76.7" @@ -14455,7 +14323,6 @@ "version": "0.76.7", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "anymatch": "^3.0.3", "debug": "^2.2.0", @@ -14481,7 +14348,6 @@ "version": "27.5.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", @@ -14497,7 +14363,6 @@ "version": "16.0.5", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/yargs-parser": "*" } @@ -14506,7 +14371,6 @@ "version": "2.6.9", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ms": "2.0.0" } @@ -14515,7 +14379,6 @@ "version": "4.0.0", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -14524,7 +14387,6 @@ "version": "27.5.1", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } @@ -14533,7 +14395,6 @@ "version": "27.5.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/types": "^27.5.1", "@types/node": "*", @@ -14550,7 +14411,6 @@ "version": "27.5.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -14564,7 +14424,6 @@ "version": "8.1.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -14578,14 +14437,12 @@ "node_modules/metro-file-map/node_modules/ms": { "version": "2.0.0", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/metro-inspector-proxy": { "version": "0.76.7", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "connect": "^3.6.5", "debug": "^2.2.0", @@ -14604,7 +14461,6 @@ "version": "2.6.9", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ms": "2.0.0" } @@ -14612,14 +14468,12 @@ "node_modules/metro-inspector-proxy/node_modules/ms": { "version": "2.0.0", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/metro-inspector-proxy/node_modules/ws": { "version": "7.5.9", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8.3.0" }, @@ -14640,7 +14494,6 @@ "version": "0.76.7", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "terser": "^5.15.0" }, @@ -14652,7 +14505,6 @@ "version": "0.76.7", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "uglify-es": "^3.1.9" }, @@ -14664,7 +14516,6 @@ "version": "0.76.7", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.20.0", "@babel/plugin-proposal-async-generator-functions": "^7.0.0", @@ -14717,7 +14568,6 @@ "version": "0.76.7", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.20.0", "babel-preset-fbjs": "^3.4.0", @@ -14736,7 +14586,6 @@ "version": "0.76.7", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=16" } @@ -14745,7 +14594,6 @@ "version": "0.76.8", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.0.0", "react-refresh": "^0.4.0" @@ -14758,7 +14606,6 @@ "version": "0.76.8", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/traverse": "^7.20.0", "@babel/types": "^7.20.0", @@ -14777,7 +14624,6 @@ "version": "0.76.8", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "invariant": "^2.2.4", "metro-source-map": "0.76.8", @@ -14797,7 +14643,6 @@ "version": "0.5.7", "dev": true, "license": "BSD-3-Clause", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -14806,7 +14651,6 @@ "version": "0.76.7", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "invariant": "^2.2.4", "metro-source-map": "0.76.7", @@ -14826,7 +14670,6 @@ "version": "0.76.7", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/traverse": "^7.20.0", "@babel/types": "^7.20.0", @@ -14845,7 +14688,6 @@ "version": "0.76.7", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=16" } @@ -14854,7 +14696,6 @@ "version": "0.5.7", "dev": true, "license": "BSD-3-Clause", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -14863,7 +14704,6 @@ "version": "0.76.7", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.20.0", "@babel/generator": "^7.20.0", @@ -14879,7 +14719,6 @@ "version": "0.76.7", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.20.0", "@babel/generator": "^7.20.0", @@ -14902,7 +14741,6 @@ "version": "0.76.7", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/traverse": "^7.20.0", "@babel/types": "^7.20.0", @@ -14921,7 +14759,6 @@ "version": "0.76.7", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=16" } @@ -14930,7 +14767,6 @@ "version": "0.5.7", "dev": true, "license": "BSD-3-Clause", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -14938,14 +14774,12 @@ "node_modules/metro/node_modules/ci-info": { "version": "2.0.0", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/metro/node_modules/debug": { "version": "2.6.9", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ms": "2.0.0" } @@ -14954,7 +14788,6 @@ "version": "4.0.0", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -14963,7 +14796,6 @@ "version": "27.5.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -14977,7 +14809,6 @@ "version": "8.1.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -14992,7 +14823,6 @@ "version": "0.76.7", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.0.0", "react-refresh": "^0.4.0" @@ -15005,7 +14835,6 @@ "version": "0.76.7", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/traverse": "^7.20.0", "@babel/types": "^7.20.0", @@ -15023,14 +14852,12 @@ "node_modules/metro/node_modules/ms": { "version": "2.0.0", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/metro/node_modules/ob1": { "version": "0.76.7", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=16" } @@ -15039,7 +14866,6 @@ "version": "0.5.7", "dev": true, "license": "BSD-3-Clause", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -15048,7 +14874,6 @@ "version": "7.5.9", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8.3.0" }, @@ -15244,7 +15069,6 @@ "version": "0.5.6", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "minimist": "^1.2.6" }, @@ -15372,7 +15196,6 @@ "version": "3.0.4", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12.0.0" } @@ -15431,7 +15254,6 @@ "version": "0.1.17", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "minimatch": "^3.0.2" }, @@ -15588,7 +15410,6 @@ "version": "1.15.0", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.12.0" }, @@ -15696,8 +15517,7 @@ "node_modules/nullthrows": { "version": "1.1.1", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/nwsapi": { "version": "2.2.7", @@ -15709,7 +15529,6 @@ "version": "0.76.8", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=16" } @@ -15892,7 +15711,6 @@ "version": "6.4.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "is-wsl": "^1.1.0" }, @@ -16234,7 +16052,6 @@ "version": "4.0.1", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -16585,6 +16402,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", @@ -16820,6 +16638,7 @@ "version": "15.8.1", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -16937,7 +16756,6 @@ "version": "6.0.2", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "inherits": "~2.0.3" } @@ -17047,6 +16865,7 @@ "version": "18.2.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -17069,7 +16888,6 @@ "version": "4.28.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "shell-quote": "^1.6.1", "ws": "^7" @@ -17079,7 +16897,6 @@ "version": "7.5.9", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8.3.0" }, @@ -17100,6 +16917,7 @@ "version": "18.2.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" @@ -17138,7 +16956,8 @@ "node_modules/react-is": { "version": "18.2.0", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/react-json-view": { "version": "1.21.3", @@ -17173,7 +16992,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "@types/react-reconciler": "^0.28.2", "its-fine": "^1.1.1", @@ -17190,7 +17008,6 @@ "version": "0.28.4", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/react": "*" } @@ -17199,7 +17016,6 @@ "version": "0.29.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" @@ -17298,7 +17114,6 @@ "version": "4.3.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -17313,7 +17128,6 @@ "version": "2.0.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "color-name": "~1.1.4" }, @@ -17324,20 +17138,17 @@ "node_modules/react-native/node_modules/color-name": { "version": "1.1.4", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/react-native/node_modules/memoize-one": { "version": "5.2.1", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/react-native/node_modules/pretty-format": { "version": "26.6.2", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/types": "^26.6.2", "ansi-regex": "^5.0.0", @@ -17352,7 +17163,6 @@ "version": "26.6.2", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", @@ -17368,7 +17178,6 @@ "version": "15.0.15", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/yargs-parser": "*" } @@ -17376,14 +17185,12 @@ "node_modules/react-native/node_modules/pretty-format/node_modules/react-is": { "version": "17.0.2", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/react-native/node_modules/promise": { "version": "8.3.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "asap": "~2.0.6" } @@ -17391,14 +17198,12 @@ "node_modules/react-native/node_modules/regenerator-runtime": { "version": "0.13.11", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/react-native/node_modules/scheduler": { "version": "0.24.0-canary-efb381bbf-20230505", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" } @@ -17407,7 +17212,6 @@ "version": "0.27.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.21.0" @@ -17423,7 +17227,6 @@ "version": "0.21.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" } @@ -17432,7 +17235,6 @@ "version": "0.4.3", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -17598,7 +17400,6 @@ "version": "2.1.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "debounce": "^1.2.1" }, @@ -17658,14 +17459,12 @@ "node_modules/readline": { "version": "1.3.0", "dev": true, - "license": "BSD", - "peer": true + "license": "BSD" }, "node_modules/recast": { "version": "0.21.5", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ast-types": "0.15.2", "esprima": "~4.0.0", @@ -17824,8 +17623,7 @@ "node_modules/require-main-filename": { "version": "2.0.0", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/requires-port": { "version": "1.0.0", @@ -17986,6 +17784,7 @@ "version": "2.79.1", "dev": true, "license": "MIT", + "peer": true, "bin": { "rollup": "dist/bin/rollup" }, @@ -18149,6 +17948,7 @@ "version": "1.66.1", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -18324,7 +18124,6 @@ "version": "2.1.0", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -18772,14 +18571,12 @@ "node_modules/stackframe": { "version": "1.3.4", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/stacktrace-parser": { "version": "0.1.10", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "type-fest": "^0.7.1" }, @@ -18791,7 +18588,6 @@ "version": "0.7.1", "dev": true, "license": "(MIT OR CC0-1.0)", - "peer": true, "engines": { "node": ">=8" } @@ -19033,8 +18829,7 @@ "node_modules/strnum": { "version": "1.0.5", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/strtok3": { "version": "6.3.0", @@ -19108,8 +18903,7 @@ "node_modules/sudo-prompt": { "version": "9.2.1", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/supports-color": { "version": "5.5.0", @@ -19137,7 +18931,6 @@ "version": "0.1.3", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { "react": ">=17.0" } @@ -19237,7 +19030,6 @@ "version": "0.8.4", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "rimraf": "~2.6.2" }, @@ -19257,7 +19049,6 @@ "version": "2.6.3", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "glob": "^7.1.3" }, @@ -19437,8 +19228,7 @@ "node_modules/throat": { "version": "5.0.0", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/through": { "version": "2.3.8", @@ -19448,7 +19238,6 @@ "version": "2.0.5", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "readable-stream": "~2.3.6", "xtend": "~4.0.1" @@ -19457,14 +19246,12 @@ "node_modules/through2/node_modules/isarray": { "version": "1.0.0", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/through2/node_modules/readable-stream": { "version": "2.3.8", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -19478,14 +19265,12 @@ "node_modules/through2/node_modules/safe-buffer": { "version": "5.1.2", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/through2/node_modules/string_decoder": { "version": "1.1.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "safe-buffer": "~5.1.0" } @@ -19636,6 +19421,7 @@ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", "dev": true, + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -19841,10 +19627,12 @@ } }, "node_modules/typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, + "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -19875,7 +19663,6 @@ "version": "3.3.9", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "commander": "~2.13.0", "source-map": "~0.6.1" @@ -19890,8 +19677,7 @@ "node_modules/uglify-es/node_modules/commander": { "version": "2.13.0", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/uid-safe": { "version": "2.1.5", @@ -20105,7 +19891,6 @@ "version": "1.2.0", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } @@ -20173,8 +19958,7 @@ "node_modules/vlq": { "version": "1.0.1", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/w3c-xmlserializer": { "version": "4.0.0", @@ -20231,6 +20015,7 @@ "version": "5.88.2", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.0", @@ -20277,6 +20062,7 @@ "version": "5.1.4", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^2.1.1", @@ -20351,6 +20137,7 @@ "version": "8.12.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -20458,6 +20245,7 @@ "version": "8.12.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -20752,8 +20540,7 @@ "node_modules/which-module": { "version": "2.0.1", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/which-typed-array": { "version": "1.1.11", @@ -20869,6 +20656,7 @@ "version": "8.12.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -21131,7 +20919,6 @@ "version": "6.2.2", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "async-limiter": "~1.0.0" } diff --git a/package.json b/package.json index 8d4626da..7ca8117f 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "@types/resize-observer-browser": "^0.1.5", "@types/sqlite3": "^3.1.8", "@types/styled-components": "^5.1.25", + "@types/wicg-file-system-access": "^2023.10.7", "@typescript-eslint/eslint-plugin": "^6.19.0", "array-move": "^4.0.0", "babel-jest": "^29.7.0", @@ -159,7 +160,7 @@ "styled-components": "^5.3.5", "ts-node": "^10.9.1", "tsconfig-paths": "^4.0.0", - "typescript": "^5.3.3", + "typescript": "^5.9.3", "webpack": "^5.84.1", "webpack-cli": "^5.1.1", "webpack-dev-server": "^4.15.0", diff --git a/shared/server/api/get_musicbill.ts b/shared/server/api/get_musicbill.ts index 36d9ac12..2ba3f4ef 100644 --- a/shared/server/api/get_musicbill.ts +++ b/shared/server/api/get_musicbill.ts @@ -1,8 +1,12 @@ import { MusicType } from '../../constants/music'; -type User = { id: string; nickname: string; avatar: string }; +interface User { + id: string; + nickname: string; + avatar: string; +} -export type Response = { +export interface Response { id: string; cover: string; name: string; @@ -23,6 +27,7 @@ export type Response = { id: string; name: string; aliases: string[]; + avatar: string; }[]; }[]; -}; +} diff --git a/shared/server/api/get_musicbill_list.ts b/shared/server/api/get_musicbill_list.ts index 31929ed7..96b37d9d 100644 --- a/shared/server/api/get_musicbill_list.ts +++ b/shared/server/api/get_musicbill_list.ts @@ -1,4 +1,8 @@ -type User = { id: string; nickname: string; avatar: string }; +interface User { + id: string; + nickname: string; + avatar: string; +} export type Response = { id: string; diff --git a/shared/server/api/get_profile.ts b/shared/server/api/get_profile.ts index 4855912d..c5e2d4d9 100644 --- a/shared/server/api/get_profile.ts +++ b/shared/server/api/get_profile.ts @@ -1,4 +1,4 @@ -export type Response = { +export interface Response { id: string; username: string; avatar: string; @@ -11,4 +11,4 @@ export type Response = { lastActiveTimestamp: number; musicPlayRecordIndate: number; twoFAEnabled: boolean; -}; +} diff --git a/shared/server/api/get_singer.ts b/shared/server/api/get_singer.ts index 8b367392..b5cf1e1c 100644 --- a/shared/server/api/get_singer.ts +++ b/shared/server/api/get_singer.ts @@ -1,6 +1,6 @@ import { MusicType } from '../../constants/music'; -export type Response = { +export interface Response { id: string; avatar: string; name: string; @@ -21,8 +21,9 @@ export type Response = { id: string; name: string; aliases: string[]; + avatar: string; }[]; }[]; editable: boolean; -}; +} diff --git a/shared/server/api/get_user.ts b/shared/server/api/get_user.ts index 1503f2d8..cf6d33dd 100644 --- a/shared/server/api/get_user.ts +++ b/shared/server/api/get_user.ts @@ -1,6 +1,6 @@ import { MusicType } from '../../constants/music'; -export type Response = { +export interface Response { id: string; avatar: string; joinTimestamp: number; @@ -23,6 +23,7 @@ export type Response = { id: string; name: string; aliases: string[]; + avatar: string; }[]; }[]; -}; +} diff --git a/shared/utils/exclude_property.ts b/shared/utils/exclude_properties.ts similarity index 56% rename from shared/utils/exclude_property.ts rename to shared/utils/exclude_properties.ts index 73977566..df7f1435 100644 --- a/shared/utils/exclude_property.ts +++ b/shared/utils/exclude_properties.ts @@ -1,11 +1,9 @@ -function excludeProperty< - Obj extends { - [key: string]: unknown; - }, +function excludeProperties< + Obj extends Record, Property extends keyof Obj, >(obj: Obj, excludeProperties: Property[]) { const properties = Object.keys(obj); - const newObj: { [key: string]: unknown } = {}; + const newObj: Record = {}; for (const property of properties) { if (excludeProperties.includes(property as Property)) { continue; @@ -13,8 +11,8 @@ function excludeProperty< newObj[property] = obj[property]; } return newObj as { - [key in Exclude]: Obj[key]; + [key in Exclude]: Obj[key]; }; } -export default excludeProperty; +export default excludeProperties; diff --git a/shared/utils/format_music_filename.ts b/shared/utils/format_music_filename.ts index f6959f9b..c030386c 100644 --- a/shared/utils/format_music_filename.ts +++ b/shared/utils/format_music_filename.ts @@ -1,4 +1,5 @@ import sanitize from 'sanitize-filename'; +import { t } from '../../apps/pwa/src/i18n'; function formatMusicFilename({ name, @@ -13,11 +14,11 @@ function formatMusicFilename({ `${ // eslint-disable-next-line no-nested-ternary singerNames.length === 0 - ? '未知歌手' + ? t('unknown_singer') : singerNames.length > 3 - ? '群星' + ? t('multiple_singers') : singerNames.join(',') - } - ${name}${ext}`, + } - ${name}.${ext}`, ); }