diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/chat_setting_add_bg.imageset/Contents.json b/Visual_Novel_iOS/Assets.xcassets/Role/chat_setting_add_bg.imageset/Contents.json new file mode 100644 index 0000000..344a87d --- /dev/null +++ b/Visual_Novel_iOS/Assets.xcassets/Role/chat_setting_add_bg.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "chat_setting_add_bg@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "chat_setting_add_bg@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/chat_setting_add_bg.imageset/chat_setting_add_bg@2x.png b/Visual_Novel_iOS/Assets.xcassets/Role/chat_setting_add_bg.imageset/chat_setting_add_bg@2x.png new file mode 100644 index 0000000..4bd2938 Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/chat_setting_add_bg.imageset/chat_setting_add_bg@2x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/chat_setting_add_bg.imageset/chat_setting_add_bg@3x.png b/Visual_Novel_iOS/Assets.xcassets/Role/chat_setting_add_bg.imageset/chat_setting_add_bg@3x.png new file mode 100644 index 0000000..9e2f380 Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/chat_setting_add_bg.imageset/chat_setting_add_bg@3x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/chat_setting_bg_delete.imageset/Contents.json b/Visual_Novel_iOS/Assets.xcassets/Role/chat_setting_bg_delete.imageset/Contents.json new file mode 100644 index 0000000..a69b230 --- /dev/null +++ b/Visual_Novel_iOS/Assets.xcassets/Role/chat_setting_bg_delete.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "chat_setting_bg_delete@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "chat_setting_bg_delete@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/chat_setting_bg_delete.imageset/chat_setting_bg_delete@2x.png b/Visual_Novel_iOS/Assets.xcassets/Role/chat_setting_bg_delete.imageset/chat_setting_bg_delete@2x.png new file mode 100644 index 0000000..8232a68 Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/chat_setting_bg_delete.imageset/chat_setting_bg_delete@2x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/chat_setting_bg_delete.imageset/chat_setting_bg_delete@3x.png b/Visual_Novel_iOS/Assets.xcassets/Role/chat_setting_bg_delete.imageset/chat_setting_bg_delete@3x.png new file mode 100644 index 0000000..64c508d Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/chat_setting_bg_delete.imageset/chat_setting_bg_delete@3x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/chat_setting_delete.imageset/Contents.json b/Visual_Novel_iOS/Assets.xcassets/Role/chat_setting_delete.imageset/Contents.json new file mode 100644 index 0000000..7b68517 --- /dev/null +++ b/Visual_Novel_iOS/Assets.xcassets/Role/chat_setting_delete.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "chat_setting_delete@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "chat_setting_delete@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/chat_setting_delete.imageset/chat_setting_delete@2x.png b/Visual_Novel_iOS/Assets.xcassets/Role/chat_setting_delete.imageset/chat_setting_delete@2x.png new file mode 100644 index 0000000..1365004 Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/chat_setting_delete.imageset/chat_setting_delete@2x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/chat_setting_delete.imageset/chat_setting_delete@3x.png b/Visual_Novel_iOS/Assets.xcassets/Role/chat_setting_delete.imageset/chat_setting_delete@3x.png new file mode 100644 index 0000000..89ce8f9 Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/chat_setting_delete.imageset/chat_setting_delete@3x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_buttle.imageset/Contents.json b/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_buttle.imageset/Contents.json new file mode 100644 index 0000000..c4d5ffb --- /dev/null +++ b/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_buttle.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "role_chat_buttle@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "role_chat_buttle@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_buttle.imageset/role_chat_buttle@2x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_buttle.imageset/role_chat_buttle@2x.png new file mode 100644 index 0000000..3cca582 Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_buttle.imageset/role_chat_buttle@2x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_buttle.imageset/role_chat_buttle@3x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_buttle.imageset/role_chat_buttle@3x.png new file mode 100644 index 0000000..1a9f283 Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_buttle.imageset/role_chat_buttle@3x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_close.imageset/Contents.json b/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_close.imageset/Contents.json new file mode 100644 index 0000000..99da427 --- /dev/null +++ b/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_close.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "role_chat_close@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "role_chat_close@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_close.imageset/role_chat_close@2x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_close.imageset/role_chat_close@2x.png new file mode 100644 index 0000000..36f3f6f Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_close.imageset/role_chat_close@2x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_close.imageset/role_chat_close@3x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_close.imageset/role_chat_close@3x.png new file mode 100644 index 0000000..f54dd2f Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_close.imageset/role_chat_close@3x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_mode.imageset/Contents.json b/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_mode.imageset/Contents.json new file mode 100644 index 0000000..aa96a17 --- /dev/null +++ b/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_mode.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "role_chat_mode@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "role_chat_mode@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_mode.imageset/role_chat_mode@2x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_mode.imageset/role_chat_mode@2x.png new file mode 100644 index 0000000..1037f53 Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_mode.imageset/role_chat_mode@2x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_mode.imageset/role_chat_mode@3x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_mode.imageset/role_chat_mode@3x.png new file mode 100644 index 0000000..97f6deb Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_chat_mode.imageset/role_chat_mode@3x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_exchange_mode.imageset/Contents.json b/Visual_Novel_iOS/Assets.xcassets/Role/role_exchange_mode.imageset/Contents.json new file mode 100644 index 0000000..b5830c4 --- /dev/null +++ b/Visual_Novel_iOS/Assets.xcassets/Role/role_exchange_mode.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "role_exchange_mode@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "role_exchange_mode@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_exchange_mode.imageset/role_exchange_mode@2x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_exchange_mode.imageset/role_exchange_mode@2x.png new file mode 100644 index 0000000..f69b122 Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_exchange_mode.imageset/role_exchange_mode@2x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_exchange_mode.imageset/role_exchange_mode@3x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_exchange_mode.imageset/role_exchange_mode@3x.png new file mode 100644 index 0000000..8602220 Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_exchange_mode.imageset/role_exchange_mode@3x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_font.imageset/Contents.json b/Visual_Novel_iOS/Assets.xcassets/Role/role_font.imageset/Contents.json new file mode 100644 index 0000000..afc9a76 --- /dev/null +++ b/Visual_Novel_iOS/Assets.xcassets/Role/role_font.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "role_font@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "role_font@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_font.imageset/role_font@2x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_font.imageset/role_font@2x.png new file mode 100644 index 0000000..f0adc54 Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_font.imageset/role_font@2x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_font.imageset/role_font@3x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_font.imageset/role_font@3x.png new file mode 100644 index 0000000..1cf10eb Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_font.imageset/role_font@3x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_music.imageset/Contents.json b/Visual_Novel_iOS/Assets.xcassets/Role/role_music.imageset/Contents.json new file mode 100644 index 0000000..d878279 --- /dev/null +++ b/Visual_Novel_iOS/Assets.xcassets/Role/role_music.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "role_music@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "role_music@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_music.imageset/role_music@2x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_music.imageset/role_music@2x.png new file mode 100644 index 0000000..ff96b20 Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_music.imageset/role_music@2x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_music.imageset/role_music@3x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_music.imageset/role_music@3x.png new file mode 100644 index 0000000..475d5a7 Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_music.imageset/role_music@3x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_new_chat.imageset/Contents.json b/Visual_Novel_iOS/Assets.xcassets/Role/role_new_chat.imageset/Contents.json new file mode 100644 index 0000000..c06b431 --- /dev/null +++ b/Visual_Novel_iOS/Assets.xcassets/Role/role_new_chat.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "role_new_chat@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "role_new_chat@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_new_chat.imageset/role_new_chat@2x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_new_chat.imageset/role_new_chat@2x.png new file mode 100644 index 0000000..6ac1514 Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_new_chat.imageset/role_new_chat@2x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_new_chat.imageset/role_new_chat@3x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_new_chat.imageset/role_new_chat@3x.png new file mode 100644 index 0000000..844348b Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_new_chat.imageset/role_new_chat@3x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_add.imageset/Contents.json b/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_add.imageset/Contents.json new file mode 100644 index 0000000..d8a11b7 --- /dev/null +++ b/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_add.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "role_setting_add@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "role_setting_add@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_add.imageset/role_setting_add@2x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_add.imageset/role_setting_add@2x.png new file mode 100644 index 0000000..03c128c Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_add.imageset/role_setting_add@2x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_add.imageset/role_setting_add@3x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_add.imageset/role_setting_add@3x.png new file mode 100644 index 0000000..bcfa64f Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_add.imageset/role_setting_add@3x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_down.imageset/Contents.json b/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_down.imageset/Contents.json new file mode 100644 index 0000000..4b0f5b4 --- /dev/null +++ b/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_down.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "role_setting_down@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "role_setting_down@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_down.imageset/role_setting_down@2x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_down.imageset/role_setting_down@2x.png new file mode 100644 index 0000000..32e380c Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_down.imageset/role_setting_down@2x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_down.imageset/role_setting_down@3x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_down.imageset/role_setting_down@3x.png new file mode 100644 index 0000000..05793f0 Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_down.imageset/role_setting_down@3x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_font_add.imageset/Contents.json b/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_font_add.imageset/Contents.json new file mode 100644 index 0000000..60cd7a8 --- /dev/null +++ b/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_font_add.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "role_setting_font_add@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "role_setting_font_add@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_font_add.imageset/role_setting_font_add@2x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_font_add.imageset/role_setting_font_add@2x.png new file mode 100644 index 0000000..afea6ff Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_font_add.imageset/role_setting_font_add@2x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_font_add.imageset/role_setting_font_add@3x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_font_add.imageset/role_setting_font_add@3x.png new file mode 100644 index 0000000..3e82d77 Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_font_add.imageset/role_setting_font_add@3x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_font_sub.imageset/Contents.json b/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_font_sub.imageset/Contents.json new file mode 100644 index 0000000..bad7518 --- /dev/null +++ b/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_font_sub.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "role_setting_font_sub@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "role_setting_font_sub@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_font_sub.imageset/role_setting_font_sub@2x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_font_sub.imageset/role_setting_font_sub@2x.png new file mode 100644 index 0000000..46b4f10 Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_font_sub.imageset/role_setting_font_sub@2x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_font_sub.imageset/role_setting_font_sub@3x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_font_sub.imageset/role_setting_font_sub@3x.png new file mode 100644 index 0000000..f100f98 Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_font_sub.imageset/role_setting_font_sub@3x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_go.imageset/Contents.json b/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_go.imageset/Contents.json new file mode 100644 index 0000000..e7d66f3 --- /dev/null +++ b/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_go.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "role_setting_go@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "role_setting_go@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_go.imageset/role_setting_go@2x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_go.imageset/role_setting_go@2x.png new file mode 100644 index 0000000..df7059c Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_go.imageset/role_setting_go@2x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_go.imageset/role_setting_go@3x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_go.imageset/role_setting_go@3x.png new file mode 100644 index 0000000..5c951e4 Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_go.imageset/role_setting_go@3x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_sub.imageset/Contents.json b/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_sub.imageset/Contents.json new file mode 100644 index 0000000..5e87dc5 --- /dev/null +++ b/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_sub.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "role_setting_sub@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "role_setting_sub@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_sub.imageset/role_setting_sub@2x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_sub.imageset/role_setting_sub@2x.png new file mode 100644 index 0000000..205da72 Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_sub.imageset/role_setting_sub@2x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_sub.imageset/role_setting_sub@3x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_sub.imageset/role_setting_sub@3x.png new file mode 100644 index 0000000..ca56cfd Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_setting_sub.imageset/role_setting_sub@3x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_talk.imageset/Contents.json b/Visual_Novel_iOS/Assets.xcassets/Role/role_talk.imageset/Contents.json new file mode 100644 index 0000000..4919b2e --- /dev/null +++ b/Visual_Novel_iOS/Assets.xcassets/Role/role_talk.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "role_talk@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "role_talk@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_talk.imageset/role_talk@2x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_talk.imageset/role_talk@2x.png new file mode 100644 index 0000000..89b5b6d Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_talk.imageset/role_talk@2x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_talk.imageset/role_talk@3x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_talk.imageset/role_talk@3x.png new file mode 100644 index 0000000..1f8e2e4 Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_talk.imageset/role_talk@3x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_text_mode.imageset/Contents.json b/Visual_Novel_iOS/Assets.xcassets/Role/role_text_mode.imageset/Contents.json new file mode 100644 index 0000000..48db03b --- /dev/null +++ b/Visual_Novel_iOS/Assets.xcassets/Role/role_text_mode.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "role_text_mode@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "role_text_mode@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_text_mode.imageset/role_text_mode@2x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_text_mode.imageset/role_text_mode@2x.png new file mode 100644 index 0000000..0f16264 Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_text_mode.imageset/role_text_mode@2x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_text_mode.imageset/role_text_mode@3x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_text_mode.imageset/role_text_mode@3x.png new file mode 100644 index 0000000..b5e6f00 Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_text_mode.imageset/role_text_mode@3x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_voice.imageset/Contents.json b/Visual_Novel_iOS/Assets.xcassets/Role/role_voice.imageset/Contents.json new file mode 100644 index 0000000..12d8a00 --- /dev/null +++ b/Visual_Novel_iOS/Assets.xcassets/Role/role_voice.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "role_voice@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "role_voice@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_voice.imageset/role_voice@2x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_voice.imageset/role_voice@2x.png new file mode 100644 index 0000000..d4de100 Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_voice.imageset/role_voice@2x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_voice.imageset/role_voice@3x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_voice.imageset/role_voice@3x.png new file mode 100644 index 0000000..f9655f4 Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_voice.imageset/role_voice@3x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/rolel_setting_selected.imageset/Contents.json b/Visual_Novel_iOS/Assets.xcassets/Role/rolel_setting_selected.imageset/Contents.json new file mode 100644 index 0000000..6efb506 --- /dev/null +++ b/Visual_Novel_iOS/Assets.xcassets/Role/rolel_setting_selected.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "rolel_setting_selected@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "rolel_setting_selected@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/rolel_setting_selected.imageset/rolel_setting_selected@2x.png b/Visual_Novel_iOS/Assets.xcassets/Role/rolel_setting_selected.imageset/rolel_setting_selected@2x.png new file mode 100644 index 0000000..7e74460 Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/rolel_setting_selected.imageset/rolel_setting_selected@2x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/rolel_setting_selected.imageset/rolel_setting_selected@3x.png b/Visual_Novel_iOS/Assets.xcassets/Role/rolel_setting_selected.imageset/rolel_setting_selected@3x.png new file mode 100644 index 0000000..cad3f6d Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/rolel_setting_selected.imageset/rolel_setting_selected@3x.png differ diff --git a/Visual_Novel_iOS/Src/Modules/Chat/Session/SessionController+Event.swift b/Visual_Novel_iOS/Src/Modules/Chat/Session/SessionController+Event.swift index 39b65fc..206a0c2 100755 --- a/Visual_Novel_iOS/Src/Modules/Chat/Session/SessionController+Event.swift +++ b/Visual_Novel_iOS/Src/Modules/Chat/Session/SessionController+Event.swift @@ -395,9 +395,16 @@ extension SessionController { // sessionNavigationView.upDownNoticeView.showUnlocked(string: "XY") inputEntrance.inputTextView.resignFirstResponder() - let vc = ChatSettingListController() - vc.aiId = aiId - navigationController?.pushViewController(vc, animated: true) +// let vc = ChatSettingListController() +// vc.aiId = aiId +// navigationController?.pushViewController(vc, animated: true) + UIView.animate(withDuration: 0.25) { + self.swipeBgView.alpha = 1.0 + self.swipeView.snp.updateConstraints { make in + make.left.equalToSuperview().offset(UIScreen.width * 0.16) + } + self.view.layoutIfNeeded() + } } @objc func tapLikeButton(){ diff --git a/Visual_Novel_iOS/Src/Modules/Chat/Session/SessionController.swift b/Visual_Novel_iOS/Src/Modules/Chat/Session/SessionController.swift index 44766fd..3e8989e 100755 --- a/Visual_Novel_iOS/Src/Modules/Chat/Session/SessionController.swift +++ b/Visual_Novel_iOS/Src/Modules/Chat/Session/SessionController.swift @@ -12,6 +12,9 @@ class SessionController: CLBaseViewController { var tableView: UITableView! // var headView: SessionAIHeadView! + var swipeView: ChatSettingSwipeView! + var swipeBgView: UIView! + // MARK: BottomViews var bottomViewsStackV : InputStackView! var toolView: UIView! @@ -197,6 +200,34 @@ extension SessionController { view.bringSubviewToFront(sessionNavigationView) view.bringSubviewToFront(bottomViewsStackV) + + swipeBgView = { + let bgView = UIView() + bgView.alpha = 0.0 + bgView.backgroundColor = UIColor.init(white: 0.0, alpha: 0.8) + view.addSubview(bgView) + bgView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(bgViewTap))) + bgView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + return bgView + }() + + swipeView = { + let v = ChatSettingSwipeView() + v.closeAction = { [weak self] in + self?.bgViewTap() + } + v.backgroundColor = .white + view.addSubview(v) + v.snp.makeConstraints { make in + make.left.equalToSuperview().inset(UIScreen.width) + make.top.bottom.equalToSuperview() + make.width.equalTo(UIScreen.width * 0.84) + } + return v + }() + } func setupUserInfo() { @@ -259,8 +290,15 @@ extension SessionController { markReadAll() } - - + @objc func bgViewTap() { + UIView.animate(withDuration: 0.25) { + self.swipeBgView.alpha = 0.0 + self.swipeView.snp.updateConstraints { make in + make.left.equalToSuperview().inset(UIScreen.width) + } + self.view.layoutIfNeeded() + } + } } diff --git a/Visual_Novel_iOS/Src/Modules/Chat/Setting/Cell/ChatBackgroundCell.swift b/Visual_Novel_iOS/Src/Modules/Chat/Setting/Cell/ChatBackgroundCell.swift new file mode 100644 index 0000000..26517d5 --- /dev/null +++ b/Visual_Novel_iOS/Src/Modules/Chat/Setting/Cell/ChatBackgroundCell.swift @@ -0,0 +1,132 @@ +// +// ChatBackgroundCell.swift +// Visual_Novel_iOS +// +// Created by mh on 2025/10/27. +// + +import UIKit +import SnapKit + +struct BackgroundRow: RowModel { + let count: Int + var cellReuseID: String { "ChatBackgroundCell" } + func cellHeight(tableWidth: CGFloat) -> CGFloat { + // 使用 UITableView.automaticDimension 让 cell 自动计算高度 + return UITableView.automaticDimension + } +} + +class ChatBackgroundCell: UITableViewCell, CellConfigurable { + + private var collectionHeight: Constraint! + private var itemCount: Int = 6 // 默认6个item + private var layout: UICollectionViewFlowLayout! + + lazy var collectionView: UICollectionView = { + layout = UICollectionViewFlowLayout() + layout.minimumLineSpacing = 10.0 + layout.minimumInteritemSpacing = 10.0 + layout.sectionInset = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20) + + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) + collectionView.delegate = self + collectionView.dataSource = self + collectionView.isScrollEnabled = false + collectionView.backgroundColor = .clear + collectionView.register(ChatBgCollectionCell.self, forCellWithReuseIdentifier: "ChatBgCollectionCell") + return collectionView + }() + + func configure(with row: RowModel) { + guard let row = row as? BackgroundRow else { return } + + // 更新数据源 + self.itemCount = row.count + + // 延迟更新,确保 collectionView 的 frame 已经确定 + DispatchQueue.main.async { [weak self] in + self?.updateCollectionViewLayout() + } + } + + private var lastCalculatedWidth: CGFloat = 0 + + private func updateCollectionViewLayout() { + guard collectionView.frame.width > 0 else { return } + + // 避免重复计算 + if abs(lastCalculatedWidth - collectionView.frame.width) < 1.0 { + return + } + lastCalculatedWidth = collectionView.frame.width + + // 重新加载数据,触发 sizeForItemAt 计算 + collectionView.reloadData() + + // 强制布局更新 + collectionView.layoutIfNeeded() + + // 获取准确的内容高度并更新约束 + let contentHeight = collectionView.collectionViewLayout.collectionViewContentSize.height + if contentHeight > 0 { + collectionHeight.update(offset: contentHeight) + } + } + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + configureViews() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func configureViews() { + contentView.addSubview(collectionView) + + collectionView.snp.makeConstraints { make in + make.top.leading.trailing.equalToSuperview() + make.width.equalToSuperview() + make.bottom.equalToSuperview().priority(999) // 让底部约束优先级稍低 + collectionHeight = make.height.equalTo(100).priority(1000).constraint // 初始占位高度 + } + } + + override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize { + // 如果 collectionView 还没有正确的 frame,先设置一个临时的 + if collectionView.frame.width == 0 { + collectionView.frame = CGRect(x: 0, y: 0, width: targetSize.width, height: 100) + updateCollectionViewLayout() + } + + // 返回计算后的尺寸 + return super.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: horizontalFittingPriority, verticalFittingPriority: verticalFittingPriority) + } + +} + +extension ChatBackgroundCell: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return itemCount + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ChatBgCollectionCell", for: indexPath) as! ChatBgCollectionCell + + // 这里可以根据 indexPath 配置不同的 cell 内容 + // 例如:cell.configure(with: data[indexPath.item]) + + return cell + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + let layout = collectionViewLayout as! UICollectionViewFlowLayout + let availableWidth = collectionView.frame.width - layout.sectionInset.left - layout.sectionInset.right + let itemWidth = (availableWidth - 2 * layout.minimumInteritemSpacing) / 3.0 + let itemHeight = itemWidth * (116.0 / 87) + return CGSize(width: itemWidth, height: itemHeight) + } +} diff --git a/Visual_Novel_iOS/Src/Modules/Chat/Setting/Cell/ChatBgCollectionCell.swift b/Visual_Novel_iOS/Src/Modules/Chat/Setting/Cell/ChatBgCollectionCell.swift new file mode 100644 index 0000000..1516bf1 --- /dev/null +++ b/Visual_Novel_iOS/Src/Modules/Chat/Setting/Cell/ChatBgCollectionCell.swift @@ -0,0 +1,84 @@ +// +// ChatBgCollectionCell.swift +// Visual_Novel_iOS +// +// Created by mh on 2025/10/28. +// + +import UIKit + +class ChatBgCollectionCell: UICollectionViewCell { + + lazy var containView: UIView = { + let v = UIView() + v.backgroundColor = UIColor(hex: "#F3F4FF") + v.cornerRadius = 10.0 + v.clipsToBounds = true + return v + }() + + lazy var contentImgView: UIImageView = { + let imgView = UIImageView() + return imgView + }() + + lazy var selectedImgView: UIImageView = { + let imgview = UIImageView(image: UIImage(named: "rolel_setting_selected")) + return imgview + }() + + lazy var deleteImgView: UIImageView = { + let imgView = UIImageView(image: UIImage(named: "chat_setting_bg_delete")) + return imgView + }() + + lazy var addImgView: UIImageView = { + let imgView = UIImageView(image: UIImage(named: "chat_setting_add_bg")) + return imgView + }() + + override init(frame: CGRect) { + super.init(frame: frame) + setupViews() + setupData() + setupEvent() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setupViews() { + contentView.addSubview(containView) + containView.addSubview(contentImgView) + containView.addSubview(addImgView) + containView.addSubview(selectedImgView) + containView.addSubview(deleteImgView) + + containView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + contentImgView.snp.makeConstraints { make in + make.edges.equalToSuperview().inset(2) + } + + addImgView.snp.makeConstraints { make in + make.centerX.centerY.equalToSuperview() + } + + selectedImgView.snp.makeConstraints { make in + make.right.bottom.equalToSuperview().inset(0) + } + + deleteImgView.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.bottom.equalToSuperview().inset(7) + } + } + + func setupData() {} + + func setupEvent() {} + +} diff --git a/Visual_Novel_iOS/Src/Modules/Chat/Setting/Cell/ChatFontCell.swift b/Visual_Novel_iOS/Src/Modules/Chat/Setting/Cell/ChatFontCell.swift new file mode 100644 index 0000000..c15e815 --- /dev/null +++ b/Visual_Novel_iOS/Src/Modules/Chat/Setting/Cell/ChatFontCell.swift @@ -0,0 +1,118 @@ +// +// ChatFontCell.swift +// Visual_Novel_iOS +// +// Created by mh on 2025/10/27. +// + +import UIKit + +// font +struct FontRow: RowModel { + let count: String + let icon: String + let title: String + var cellReuseID: String { "ChatFontCell" } + func cellHeight(tableWidth: CGFloat) -> CGFloat { 50 } +} + +class ChatFontCell: ChatSettingBaseCell, CellConfigurable { + + lazy var iconImgView: UIImageView = { + let imgView = UIImageView(image: UIImage(named: "role_exchange_mode")) + return imgView + }() + + lazy var titleLab: UILabel = { + let lab = UILabel() + lab.text = "XL-0826-32K" + lab.font = UIFont.boldSystemFont(ofSize: 14) + lab.textColor = UIColor(hex: "#666666") + return lab + }() + + lazy var fontSub: UIButton = { + let btn = UIButton() + btn.setImage(UIImage(named: "role_setting_font_sub"), for: .normal) + btn.addTarget(self, action: #selector(fontSubTap), for: .touchUpInside) + return btn + }() + + lazy var fontAdd: UIButton = { + let btn = UIButton() + btn.setImage(UIImage(named: "role_setting_font_add"), for: .normal) + btn.addTarget(self, action: #selector(fontAddTap), for: .touchUpInside) + return btn + }() + + lazy var fontLab: UILabel = { + let lab = UILabel() + lab.text = "20" + lab.font = UIFont.systemFont(ofSize: 14) + lab.textColor = UIColor(hex: "#999999") + lab.textAlignment = .center + return lab + }() + + func configure(with row: RowModel) { + guard let row = row as? FontRow else { return } + titleLab.text = row.title + iconImgView.image = UIImage(named: row.icon) + } + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + configureViews() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func configureViews() { + + containerView.addSubview(iconImgView) + containerView.addSubview(titleLab) + containerView.addSubview(fontSub) + containerView.addSubview(fontAdd) + containerView.addSubview(fontLab) + + iconImgView.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalToSuperview().offset(12) + make.width.height.equalTo(21) + } + + titleLab.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalTo(iconImgView.snp.right).offset(9) + make.right.equalTo(fontSub.snp.left).offset(-5) + } + + fontAdd.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.right.equalToSuperview().inset(20) + make.width.height.equalTo(40) + } + + fontLab.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.right.equalTo(fontAdd.snp.left).offset(-10) + } + + fontSub.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.right.equalTo(fontLab.snp.left).offset(-10) + make.width.height.equalTo(40) + } + } + + @objc func fontSubTap() { + print("sub sub sub") + } + + @objc func fontAddTap() { + print("add add add") + } +} diff --git a/Visual_Novel_iOS/Src/Modules/Chat/Setting/Cell/ChatHistoryCell.swift b/Visual_Novel_iOS/Src/Modules/Chat/Setting/Cell/ChatHistoryCell.swift new file mode 100644 index 0000000..766f50f --- /dev/null +++ b/Visual_Novel_iOS/Src/Modules/Chat/Setting/Cell/ChatHistoryCell.swift @@ -0,0 +1,204 @@ +// +// ChatHistoryCell.swift +// Visual_Novel_iOS +// +// Created by mh on 2025/10/27. +// + +import Foundation +import SnapKit + +struct HistoryRow: RowModel { + let time: String + let icon: String + let title: String + let itemCount: Int // 添加数据项数量 + var cellReuseID: String { "ChatHistoryCell" } + func cellHeight(tableWidth: CGFloat) -> CGFloat { + // 使用更准确的高度计算 + let singleCellHeight = ChatHistoryContentCell.calculateHeight(for: tableWidth) + let deleteButtonHeight: CGFloat = 62 // DELETE按钮高度(footer view) + let totalHeight = CGFloat(itemCount) * singleCellHeight + deleteButtonHeight + return totalHeight + } +} + + +class ChatHistoryCell: UITableViewCell, CellConfigurable { + + private var tableViewHeight: Constraint! + private var itemCount: Int = 3 // 默认3个item + private var lastCalculatedHeight: CGFloat = 0 + private var isLayoutConfigured = false + + lazy var tableView: UITableView = { + let tableView = UITableView(frame: .zero, style: .plain) + tableView.separatorStyle = .none + tableView.delegate = self + tableView.dataSource = self + tableView.estimatedRowHeight = 60 // 更准确的预估高度 + tableView.rowHeight = UITableView.automaticDimension + tableView.backgroundColor = .clear + tableView.showsVerticalScrollIndicator = false + tableView.register(ChatHistoryContentCell.self, forCellReuseIdentifier: "ChatHistoryContentCell") + tableView.contentInset = .zero + tableView.isScrollEnabled = false + tableView.translatesAutoresizingMaskIntoConstraints = false + return tableView + }() + + func configure(with row: RowModel) { + guard let row = row as? HistoryRow else { return } + + // 更新数据源 + self.itemCount = row.itemCount + + // 立即更新tableView数据 + tableView.reloadData() + + // 如果布局已经配置,立即更新高度 + if isLayoutConfigured { + updateTableViewLayout() + } + } + + private func updateTableViewLayout() { + // 确保tableView已经布局完成 + tableView.layoutIfNeeded() + + // 强制计算contentSize + tableView.setNeedsLayout() + tableView.layoutIfNeeded() + + // tableView.contentSize.height 已经包含了所有内容的高度,包括footer view + let totalH = tableView.contentSize.height + + // 避免死循环和无效更新 + guard abs(lastCalculatedHeight - totalH) > 1.0 else { return } + lastCalculatedHeight = totalH + + // 更新高度约束 + tableViewHeight.update(offset: totalH) + + // 通知父tableView更新布局 + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + + // 通知父tableView需要更新高度 + if let tableView = self.superview as? UITableView { + tableView.beginUpdates() + tableView.endUpdates() + } else { + // 如果不是直接父tableView,则向上查找 + var currentView = self.superview + while currentView != nil { + if let tableView = currentView as? UITableView { + tableView.beginUpdates() + tableView.endUpdates() + break + } + currentView = currentView?.superview + } + } + } + } + + override func layoutSubviews() { + super.layoutSubviews() + + // 确保Cell本身宽度确定后再计算 + guard contentView.bounds.width > 0 else { return } + + // 设置tableView的宽度 + tableView.frame.size.width = contentView.bounds.width + + // 标记布局已配置 + if !isLayoutConfigured { + isLayoutConfigured = true + } + + // 更新tableView布局 + updateTableViewLayout() + } + + + @objc func deleteBtnTap() { + + } + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + configureViews() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func configureViews() { + contentView.addSubview(tableView) + + tableView.snp.makeConstraints { make in + make.top.left.right.equalToSuperview() + make.width.equalToSuperview() + make.bottom.equalToSuperview().priority(.low) // 让底部约束优先级稍低 + tableViewHeight = make.height.equalTo(100).priority(1000).constraint // 初始占位高度 + } + + // 确保tableView的约束正确设置 + tableView.setContentHuggingPriority(.required, for: .vertical) + tableView.setContentCompressionResistancePriority(.required, for: .vertical) + } + +} + +extension ChatHistoryCell: UITableViewDelegate, UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return itemCount + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "ChatHistoryContentCell", for: indexPath) as! ChatHistoryContentCell + cell.selectionStyle = .none + + // 这里可以根据 indexPath 配置不同的 cell 内容 + // 例如:cell.configure(with: data[indexPath.item]) + + return cell + } + + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + return CGFLOAT_MIN + } + + func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { + return UITableView.automaticDimension + } + + func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { + let footer = UIView() + footer.backgroundColor = .clear + + let deleteBtn = UIButton() + deleteBtn.cornerRadius = 25.0 + deleteBtn.borderColor = UIColor(hex: "#FF3B30") + deleteBtn.borderWidth = 2.0 + deleteBtn.setTitle("DELETE ", for: .normal) + deleteBtn.setTitleColor(UIColor(hex: "#FF3B30"), for: .normal) + deleteBtn.setImage(UIImage(named: "chat_setting_delete"), for: .normal) + deleteBtn.titleLabel?.font = UIFont.boldSystemFont(ofSize: 17.0) + deleteBtn.setUp(.default, padding: 5.0) + deleteBtn.addTarget(self, action: #selector(deleteBtnTap), for: .touchUpInside) + footer.addSubview(deleteBtn) + + deleteBtn.snp.makeConstraints { make in + make.top.equalToSuperview().offset(10) + make.bottom.equalToSuperview().inset(2) + make.left.right.equalToSuperview().inset(20) + make.height.equalTo(50) + } + + return footer + } +} diff --git a/Visual_Novel_iOS/Src/Modules/Chat/Setting/Cell/ChatHistoryContentCell.swift b/Visual_Novel_iOS/Src/Modules/Chat/Setting/Cell/ChatHistoryContentCell.swift new file mode 100644 index 0000000..8319603 --- /dev/null +++ b/Visual_Novel_iOS/Src/Modules/Chat/Setting/Cell/ChatHistoryContentCell.swift @@ -0,0 +1,124 @@ +// +// ChatHistoryContentCell.swift +// Visual_Novel_iOS +// +// Created by mh on 2025/10/27. +// + +import UIKit + +class ChatHistoryContentCell: UITableViewCell { + + lazy var containerView: UIView = { + let view = UIView() + view.backgroundColor = UIColor(hex: "#F6F6F6") + view.cornerRadius = 15.0 + return view + }() + + lazy var iconImgView: UIImageView = { + let imgView = UIImageView() + imgView.cornerRadius = 12.5 + imgView.backgroundColor = .blue + return imgView + }() + + lazy var timeLab: UILabel = { + let lab = UILabel() + lab.text = "2025/09/26 17:30" + lab.textColor = UIColor.hexString("#999999", alpha: 0.6) + lab.font = UIFont.systemFont(ofSize: 12) + return lab + }() + + lazy var contentLab: UILabel = { + let lab = UILabel() + lab.text = "The Boss Fell for Me: My Days Screwing Nuts at Foxconn" + lab.textColor = UIColor(hex: "#666666") + lab.font = UIFont.systemFont(ofSize: 14) + lab.numberOfLines = 2 + return lab + }() + + lazy var arrowImgView: UIImageView = { + let imgView = UIImageView(image: UIImage(named: "role_setting_go")) + return imgView + }() + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + configureViews() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func configureViews() { + + contentView.addSubview(containerView) + containerView.addSubview(iconImgView) + containerView.addSubview(timeLab) + containerView.addSubview(contentLab) + containerView.addSubview(arrowImgView) + + containerView.snp.makeConstraints { make in + make.left.right.equalToSuperview().inset(20) + make.top.bottom.equalToSuperview().inset(2.5) + } + + iconImgView.snp.makeConstraints { make in + make.left.equalToSuperview().inset(12) + make.top.equalToSuperview().offset(10) + make.width.height.equalTo(25) + } + + timeLab.snp.makeConstraints { make in + make.left.equalTo(iconImgView.snp.right).offset(8) + make.top.equalTo(iconImgView.snp.top) + make.right.equalTo(arrowImgView.snp.left).offset(-15) + } + + contentLab.snp.makeConstraints { make in + make.left.equalTo(timeLab.snp.left) + make.right.equalTo(timeLab.snp.right).offset(-15) + make.top.equalTo(timeLab.snp.bottom).offset(6) + make.bottom.equalToSuperview().inset(10) + } + + arrowImgView.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.right.equalToSuperview().inset(15) + make.width.height.equalTo(15) + } + + // 设置内容优先级,确保自动布局正确计算高度 + contentLab.setContentHuggingPriority(.required, for: .vertical) + contentLab.setContentCompressionResistancePriority(.required, for: .vertical) + } + + // 计算cell的准确高度 + static func calculateHeight(for width: CGFloat) -> CGFloat { + let containerWidth = width - 40 // 左右各20的inset + let iconHeight: CGFloat = 25 + let iconTopMargin: CGFloat = 10 + let timeToContentMargin: CGFloat = 6 + let contentBottomMargin: CGFloat = 10 + let cellTopBottomMargin: CGFloat = 5 // 上下各2.5的inset + + // 计算时间标签高度 + let timeHeight: CGFloat = 12 // 字体大小12 + + // 计算内容标签高度(2行文本) + let contentFont = UIFont.systemFont(ofSize: 14) + let contentWidth = containerWidth - 12 - 25 - 8 - 15 - 15 // 减去各种边距 + let contentHeight = contentFont.lineHeight * 2 // 2行文本 + + let totalHeight = cellTopBottomMargin + iconTopMargin + max(iconHeight, timeHeight) + timeToContentMargin + contentHeight + contentBottomMargin + cellTopBottomMargin + + return totalHeight + } + + +} diff --git a/Visual_Novel_iOS/Src/Modules/Chat/Setting/Cell/ChatResponseTokenCell.swift b/Visual_Novel_iOS/Src/Modules/Chat/Setting/Cell/ChatResponseTokenCell.swift new file mode 100644 index 0000000..d8988f4 --- /dev/null +++ b/Visual_Novel_iOS/Src/Modules/Chat/Setting/Cell/ChatResponseTokenCell.swift @@ -0,0 +1,91 @@ +// +// ChatResponseTokenCell.swift +// Visual_Novel_iOS +// +// Created by mh on 2025/10/27. +// + +import UIKit + +// token +struct TokenRow: RowModel { + let count: String + var cellReuseID: String { "ChatResponseTokenCell" } + func cellHeight(tableWidth: CGFloat) -> CGFloat { 50 } +} + +class ChatResponseTokenCell: ChatSettingBaseCell, CellConfigurable { + + lazy var countLab: UILabel = { + let lab = UILabel() + lab.textColor = UIColor(hex: "#666666") + lab.font = UIFont.boldSystemFont(ofSize: 14) + lab.text = "0" + lab.textAlignment = .center + return lab + }() + + lazy var subBtn: UIButton = { + let btn = UIButton() + btn.setImage(UIImage(named: "role_setting_sub"), for: .normal) + btn.addTarget(self, action: #selector(subBtnTap), for: .touchUpInside) + return btn + }() + + lazy var addBtn: UIButton = { + let btn = UIButton() + btn.setImage(UIImage(named: "role_setting_add"), for: .normal) + btn.addTarget(self, action: #selector(addBtnTap), for: .touchUpInside) + return btn + }() + + @objc func subBtnTap() { + + } + + @objc func addBtnTap() { + + } + + func configure(with row: RowModel) { + guard let row = row as? TokenRow else { return } + countLab.text = row.count + } + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + configureViews() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func configureViews() { + + containerView.addSubview(subBtn) + containerView.addSubview(countLab) + containerView.addSubview(addBtn) + + countLab.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalTo(subBtn.snp.right).offset(5) + make.right.equalTo(addBtn.snp.left).offset(-5) + } + + subBtn.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalToSuperview().inset(10) + make.width.equalTo(25) + make.height.equalTo(25) + } + + addBtn.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.right.equalToSuperview().inset(10) + make.width.equalTo(25) + make.height.equalTo(25) + } + } +} diff --git a/Visual_Novel_iOS/Src/Modules/Chat/Setting/Cell/ChatSettingBaseCell.swift b/Visual_Novel_iOS/Src/Modules/Chat/Setting/Cell/ChatSettingBaseCell.swift new file mode 100644 index 0000000..d5f2a1e --- /dev/null +++ b/Visual_Novel_iOS/Src/Modules/Chat/Setting/Cell/ChatSettingBaseCell.swift @@ -0,0 +1,39 @@ +// +// ChatSettingBaseCell.swift +// Visual_Novel_iOS +// +// Created by mh on 2025/10/27. +// + +import UIKit + +class ChatSettingBaseCell: UITableViewCell { + + lazy var containerView: UIView = { + let view = UIView() + view.backgroundColor = UIColor(hex: "#F6F6F6") + view.cornerRadius = 15.0 + return view + }() + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + setupViews() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setupViews() { + + contentView.addSubview(containerView) + + containerView.snp.makeConstraints { make in + make.left.right.equalToSuperview().inset(20) + make.top.bottom.equalToSuperview().inset(2.5) + make.height.equalTo(45.0) + } + } +} diff --git a/Visual_Novel_iOS/Src/Modules/Chat/Setting/Cell/ChatSwipeCell.swift b/Visual_Novel_iOS/Src/Modules/Chat/Setting/Cell/ChatSwipeCell.swift new file mode 100644 index 0000000..b27fe5c --- /dev/null +++ b/Visual_Novel_iOS/Src/Modules/Chat/Setting/Cell/ChatSwipeCell.swift @@ -0,0 +1,120 @@ +// +// ChatSwipeCell.swift +// Visual_Novel_iOS +// +// Created by mh on 2025/10/27. +// + +import UIKit + +// icon title 声音头像 箭头 +struct ImageRow: RowModel { + let icon: String + let title: String + let showAvatar: Bool + let showArrow: Bool + let showSwitch: Bool + var cellReuseID: String { "ChatSwipeCell" } + func cellHeight(tableWidth: CGFloat) -> CGFloat { 50 } +} + +class ChatSwipeCell: ChatSettingBaseCell, CellConfigurable { + + lazy var iconImgView: UIImageView = { + let imgView = UIImageView(image: UIImage(named: "role_exchange_mode")) + return imgView + }() + + lazy var titleLab: UILabel = { + let lab = UILabel() + lab.text = "XL-0826-32K" + lab.font = UIFont.boldSystemFont(ofSize: 14) + lab.textColor = UIColor(hex: "#666666") + return lab + }() + + lazy var avatarView : UIImageView = { + let imgView = UIImageView() + imgView.cornerRadius = 10.5 + imgView.backgroundColor = .blue + return imgView + }() + + lazy var arrowImgView: UIImageView = { + let imgView = UIImageView(image: UIImage(named: "role_setting_go")) + return imgView + }() + + lazy var switchControl: SevenSwitch = { + let con = SevenSwitch() + con.onTintColor = UIColor(hex: "#020025") + con.onThumbTintColor = UIColor(hex: "#00CC88") + con.inactiveColor = UIColor(hex: "#020025") + return con + }() + + lazy var stackView: UIStackView = { + let stackView = UIStackView(arrangedSubviews: [avatarView, arrowImgView]) + stackView.spacing = 5 + stackView.distribution = .fill + stackView.alignment = .center + return stackView + }() + + func configure(with row: RowModel) { + guard let row = row as? ImageRow else { return } + iconImgView.image = UIImage(named: row.icon) + titleLab.text = row.title + avatarView.isHidden = !row.showAvatar + arrowImgView.isHidden = !row.showArrow + switchControl.isHidden = !row.showSwitch + } + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + configureViews() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func configureViews() { + containerView.addSubview(iconImgView) + containerView.addSubview(titleLab) + containerView.addSubview(stackView) + containerView.addSubview(switchControl) + + iconImgView.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalToSuperview().offset(12) + make.width.height.equalTo(21) + } + + avatarView.snp.makeConstraints { make in + make.width.height.equalTo(21) + } + + stackView.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.right.equalToSuperview().inset(15) + } + + titleLab.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalTo(iconImgView.snp.right).offset(9) + make.right.equalTo(stackView.snp.left).offset(-5) + } + + switchControl.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.right.equalToSuperview().inset(15) + make.width.equalTo(45) + make.height.equalTo(23) + } + + titleLab.setContentCompressionResistancePriority(.fittingSizeLevel, for: .horizontal) + titleLab.setContentHuggingPriority(.fittingSizeLevel, for: .horizontal) + } +} diff --git a/Visual_Novel_iOS/Src/Modules/Chat/Setting/CellProtocol/ActionProtocol.swift b/Visual_Novel_iOS/Src/Modules/Chat/Setting/CellProtocol/ActionProtocol.swift new file mode 100644 index 0000000..4d2bc62 --- /dev/null +++ b/Visual_Novel_iOS/Src/Modules/Chat/Setting/CellProtocol/ActionProtocol.swift @@ -0,0 +1,19 @@ +// +// ActionProtocol.swift +// Visual_Novel_iOS +// +// Created by mh on 2025/10/27. +// + +import Foundation + +/// 1. 数据协议:每行只要告诉我复用ID 和 高度 +protocol RowModel { + var cellReuseID: String { get } + func cellHeight(tableWidth: CGFloat) -> CGFloat +} + +/// 2. 渲染协议:Cell 自己负责填数据 +protocol CellConfigurable: AnyObject { + func configure(with row: RowModel) +} diff --git a/Visual_Novel_iOS/Src/Modules/Chat/Setting/View/ChatSettingSwipeView.swift b/Visual_Novel_iOS/Src/Modules/Chat/Setting/View/ChatSettingSwipeView.swift new file mode 100644 index 0000000..1f60177 --- /dev/null +++ b/Visual_Novel_iOS/Src/Modules/Chat/Setting/View/ChatSettingSwipeView.swift @@ -0,0 +1,196 @@ +// +// ChatSettingSwipeView.swift +// Visual_Novel_iOS +// +// Created by mh on 2025/10/24. +// + +import UIKit + +class ChatSettingSwipeView: CLContainer { + + var closeAction: (()->Void)? + + var sectionTitle: [String] = ["Switch Model", "Sound", "Maximum number of response tokens Maximum number of response tokens", "Appearance", "Background", "Historical Archives"] + var rows: [[RowModel]] = [ + [ImageRow(icon: "role_exchange_mode", title: "XL-0826-32K", showAvatar: false, showArrow: true, showSwitch: false), ImageRow(icon: "role_text_mode", title: "Short Text Mode", showAvatar: false, showArrow: true, showSwitch: false)], + [ImageRow(icon: "role_voice", title: "Voice actor", showAvatar: true, showArrow: true, showSwitch: false), ImageRow(icon: "role_talk", title: "Play dialogue only", showAvatar: false, showArrow: false, showSwitch: true)], + [TokenRow(count: "2500")], + [FontRow(count: "20", icon: "role_font", title: "Font size"), ImageRow(icon: "role_chat_mode", title: "Chat Mode", showAvatar: false, showArrow: true, showSwitch: false), ImageRow(icon: "role_chat_buttle", title: "Chat buttle", showAvatar: false, showArrow: true, showSwitch: false)], + [BackgroundRow(count: 50)], + [HistoryRow(time: "", icon: "", title: "", itemCount: 30)] + ] + + + lazy var titleLab: UILabel = { + let lab = UILabel() + lab.text = "Setting" + lab.font = UIFont.boldSystemFont(ofSize: 18) + lab.textColor = UIColor(hex: "#000000") + return lab + }() + + lazy var closeBtn: UIButton = { + let btn = UIButton(type: .custom) + btn.setImage(UIImage(named: "role_chat_close"), for: .normal) + btn.addTarget(self, action: #selector(closeBtnTap), for: .touchUpInside) + return btn + }() + + lazy var newChatBtn: UIButton = { + let btn = UIButton(type: .custom) + btn.setBackgroundImage(UIImage(named: "role_new_chat"), for: .normal) + btn.addTarget(self, action: #selector(newChatBtnTap), for: .touchUpInside) + btn.setTitle("+ Start New Chat", for: .normal) + btn.titleLabel?.font = UIFont.boldSystemFont(ofSize: 17) + btn.imageView?.contentMode = .scaleToFill + return btn + }() + + lazy var tableView: UITableView = { + let tableView = UITableView(frame: .zero, style: .grouped) + tableView.separatorStyle = .none + tableView.delegate = self + tableView.dataSource = self + tableView.estimatedRowHeight = 72 + tableView.rowHeight = UITableView.automaticDimension + tableView.backgroundColor = .clear + tableView.showsVerticalScrollIndicator = false + tableView.tableFooterView = UIView() + tableView.register(ChatSwipeCell.self, forCellReuseIdentifier: "ChatSwipeCell") + tableView.register(ChatResponseTokenCell.self, forCellReuseIdentifier: "ChatResponseTokenCell") + tableView.register(ChatFontCell.self, forCellReuseIdentifier: "ChatFontCell") + tableView.register(ChatBackgroundCell.self, forCellReuseIdentifier: "ChatBackgroundCell") + tableView.register(ChatHistoryCell.self, forCellReuseIdentifier: "ChatHistoryCell") + tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) + return tableView + }() + + + override init(frame: CGRect) { + super.init(frame: frame) + + setupViews() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setupViews() { + + navigationView = NavigationView() + navigationView?.backButton.isHidden = false + navigationView?.isHidden = true + addSubview(navigationView ?? UIView()) + + addSubview(titleLab) + addSubview(closeBtn) + addSubview(tableView) + addSubview(newChatBtn) + + navigationView?.snp.makeConstraints { make in + make.top.equalToSuperview() + make.left.equalToSuperview() + make.right.equalToSuperview() + make.height.equalTo(UIWindow.navBarTotalHeight) + } + + titleLab.snp.makeConstraints { make in + make.left.equalToSuperview().offset(20) + if self.navigationView != nil { + make.centerY.equalTo(self.navigationView!.titleLabel.snp.centerY) + } else { + make.top.equalToSuperview().offset(UIDevice().statusBarHeight + 13.0) + } + + make.right.equalTo(closeBtn.snp.left).offset(-10) + } + + closeBtn.snp.makeConstraints { make in + make.centerY.equalTo(titleLab.snp.centerY) + make.right.equalToSuperview().inset(20) + make.width.height.equalTo(25) + } + + + newChatBtn.snp.makeConstraints { make in + make.left.right.equalToSuperview().inset(20) + make.height.equalTo(50) + make.bottom.equalToSuperview().inset(UIDevice().safeBottom + 5.0) + } + + tableView.snp.makeConstraints { make in + make.left.right.equalToSuperview() + make.top.equalTo(closeBtn.snp.bottom).offset(10) + make.bottom.equalTo(newChatBtn.snp.top).offset(-5) + } + + // 设置tableView的contentInset,让内容可以滑动到最顶部 + tableView.contentInsetAdjustmentBehavior = .never + tableView.automaticallyAdjustsScrollIndicatorInsets = false + + } +} + +extension ChatSettingSwipeView { + + @objc func closeBtnTap() { + self.closeAction?() + } + + @objc func newChatBtnTap() { + + } +} + +extension ChatSettingSwipeView: UITableViewDelegate, UITableViewDataSource { + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + // 确保tableView内容可以滑动到最顶部 + // 不需要额外的contentInset调整 + } + + func numberOfSections(in tableView: UITableView) -> Int { + return rows.count + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return rows[section].count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let row = rows[indexPath.section][indexPath.row] + let cell = tableView.dequeueReusableCell(withIdentifier: row.cellReuseID, for: indexPath) + cell.selectionStyle = .none + (cell as? CellConfigurable)?.configure(with: row) + return cell + } + + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + let header = UIView() + + let lab = UILabel() + lab.text = sectionTitle[section] + lab.textColor = UIColor(hex: "#333333") + lab.font = UIFont.boldSystemFont(ofSize: 16) + lab.numberOfLines = 0 + header.addSubview(lab) + lab.snp.makeConstraints { make in + make.left.equalToSuperview().offset(20) + make.right.equalToSuperview() + make.top.equalToSuperview().inset(5) + make.bottom.equalToSuperview() + } + + return header + } + + func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { + return UIView() + } + + func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { + return CGFLOAT_MIN + } +} diff --git a/Visual_Novel_iOS/Src/Modules/Chat/Setting/View/SevenSwitch.swift b/Visual_Novel_iOS/Src/Modules/Chat/Setting/View/SevenSwitch.swift new file mode 100644 index 0000000..fde4935 --- /dev/null +++ b/Visual_Novel_iOS/Src/Modules/Chat/Setting/View/SevenSwitch.swift @@ -0,0 +1,543 @@ +// +// SevenSwitch.swift +// +// Created by Benjamin Vogelzang on 6/20/14. +// Copyright (c) 2014 Ben Vogelzang. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import UIKit +import QuartzCore + +@IBDesignable @objc open class SevenSwitch: UIControl { + + // public + + /* + * Set (without animation) whether the switch is on or off + */ + @IBInspectable open var on: Bool { + get { + return switchValue + } + set { + switchValue = newValue + self.setOn(newValue, animated: false) + } + } + + /* + * Sets the background color that shows when the switch off and actively being touched. + * Defaults to light gray. + */ + @IBInspectable open var activeColor: UIColor = UIColor(red: 0.89, green: 0.89, blue: 0.89, alpha: 1) { + willSet { + if self.on && !self.isTracking { + backgroundView.backgroundColor = newValue + } + } + } + + /* + * Sets the background color when the switch is off. + * Defaults to clear color. + */ + @IBInspectable open var inactiveColor: UIColor = UIColor.clear { + willSet { + if !self.on && !self.isTracking { + backgroundView.backgroundColor = newValue + } + } + } + + /* + * Sets the background color that shows when the switch is on. + * Defaults to green. + */ + @IBInspectable open var onTintColor: UIColor = UIColor(red: 0.3, green: 0.85, blue: 0.39, alpha: 1) { + willSet { + if self.on && !self.isTracking { + backgroundView.backgroundColor = newValue + backgroundView.layer.borderColor = newValue.cgColor + } + } + } + + /* + * Sets the border color that shows when the switch is off. Defaults to light gray. + */ + @IBInspectable open var bordersColor: UIColor = UIColor(red: 0.78, green: 0.78, blue: 0.8, alpha: 1) { + willSet { + if !self.on { + backgroundView.layer.borderColor = newValue.cgColor + } + } + } + + /* + * Sets the knob color. Defaults to white. + */ + @IBInspectable open var thumbTintColor: UIColor = UIColor.white { + willSet { + if !userDidSpecifyOnThumbTintColor { + onThumbTintColor = newValue + } + if (!userDidSpecifyOnThumbTintColor || !self.on) && !self.isTracking { + thumbView.backgroundColor = newValue + } + } + } + + /* + * Sets the knob color that shows when the switch is on. Defaults to white. + */ + @IBInspectable open var onThumbTintColor: UIColor = UIColor.white { + willSet { + userDidSpecifyOnThumbTintColor = true + if self.on && !self.isTracking { + thumbView.backgroundColor = newValue + } + } + } + + /* + * Sets the shadow color of the knob. Defaults to gray. + */ + @IBInspectable open var shadowColor: UIColor = UIColor.gray { + willSet { + thumbView.layer.shadowColor = newValue.cgColor + } + } + + /* + * Sets whether or not the switch edges are rounded. + * Set to NO to get a stylish square switch. + * Defaults to YES. + */ + @IBInspectable open var isRounded: Bool = true { + willSet { + if newValue { + backgroundView.layer.cornerRadius = self.frame.size.height * 0.5 + thumbView.layer.cornerRadius = (self.frame.size.height * 0.5) - 1 + } + else { + backgroundView.layer.cornerRadius = 2 + thumbView.layer.cornerRadius = 2 + } + + thumbView.layer.shadowPath = UIBezierPath(roundedRect: thumbView.bounds, cornerRadius: thumbView.layer.cornerRadius).cgPath + } + } + + /* + * Sets the image that shows on the switch thumb. + */ + @IBInspectable open var thumbImage: UIImage! { + willSet { + thumbImageView.image = newValue + } + } + + /* + * Sets the image that shows when the switch is on. + * The image is centered in the area not covered by the knob. + * Make sure to size your images appropriately. + */ + @IBInspectable open var onImage: UIImage! { + willSet { + onImageView.image = newValue + } + } + + /* + * Sets the image that shows when the switch is off. + * The image is centered in the area not covered by the knob. + * Make sure to size your images appropriately. + */ + @IBInspectable open var offImage: UIImage! { + willSet { + offImageView.image = newValue + } + } + + /* + * Sets the text that shows when the switch is on. + * The text is centered in the area not covered by the knob. + */ + open var onLabel: UILabel! + + /* + * Sets the text that shows when the switch is off. + * The text is centered in the area not covered by the knob. + */ + open var offLabel: UILabel! + + // internal + internal var backgroundView: UIView! + internal var thumbView: UIView! + internal var onImageView: UIImageView! + internal var offImageView: UIImageView! + internal var thumbImageView: UIImageView! + // private + fileprivate var currentVisualValue: Bool = false + fileprivate var startTrackingValue: Bool = false + fileprivate var didChangeWhileTracking: Bool = false + fileprivate var isAnimating: Bool = false + fileprivate var userDidSpecifyOnThumbTintColor: Bool = false + fileprivate var switchValue: Bool = false + + /* + * Initialization + */ + public convenience init() { + self.init(frame: CGRect(x: 0, y: 0, width: 50, height: 30)) + } + + required public init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + + self.setup() + } + + override public init(frame: CGRect) { + let initialFrame = frame.isEmpty ? CGRect(x: 0, y: 0, width: 50, height: 30) : frame + super.init(frame: initialFrame) + + self.setup() + } + + + /* + * Setup the individual elements of the switch and set default values + */ + fileprivate func setup() { + + // background + self.backgroundView = UIView(frame: CGRect(x: 0, y: 0, width: self.frame.size.width, height: self.frame.size.height)) + backgroundView.backgroundColor = UIColor.clear + backgroundView.layer.cornerRadius = self.frame.size.height * 0.5 + backgroundView.layer.borderColor = self.bordersColor.cgColor + backgroundView.layer.borderWidth = 0.0 + backgroundView.isUserInteractionEnabled = false + backgroundView.clipsToBounds = true + self.addSubview(backgroundView) + + // on/off images + self.onImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: self.frame.size.width - self.frame.size.height, height: self.frame.size.height)) + onImageView.alpha = 1.0 + onImageView.contentMode = UIView.ContentMode.center + backgroundView.addSubview(onImageView) + + self.offImageView = UIImageView(frame: CGRect(x: self.frame.size.height, y: 0, width: self.frame.size.width - self.frame.size.height, height: self.frame.size.height)) + offImageView.alpha = 1.0 + offImageView.contentMode = UIView.ContentMode.center + backgroundView.addSubview(offImageView) + + // labels + self.onLabel = UILabel(frame: CGRect(x: 0, y: 0, width: self.frame.size.width - self.frame.size.height, height: self.frame.size.height)) + onLabel.textAlignment = NSTextAlignment.center + onLabel.textColor = UIColor.lightGray + onLabel.font = UIFont.systemFont(ofSize: 12) + backgroundView.addSubview(onLabel) + + self.offLabel = UILabel(frame: CGRect(x: self.frame.size.height, y: 0, width: self.frame.size.width - self.frame.size.height, height: self.frame.size.height)) + offLabel.textAlignment = NSTextAlignment.center + offLabel.textColor = UIColor.lightGray + offLabel.font = UIFont.systemFont(ofSize: 12) + backgroundView.addSubview(offLabel) + + // thumb + self.thumbView = UIView(frame: CGRect(x: 1, y: 1, width: self.frame.size.height - 2, height: self.frame.size.height - 2)) + thumbView.backgroundColor = self.thumbTintColor + thumbView.layer.cornerRadius = (self.frame.size.height * 0.5) - 1 + thumbView.layer.shadowColor = self.shadowColor.cgColor + thumbView.layer.shadowRadius = 2.0 + thumbView.layer.shadowOpacity = 0.5 + thumbView.layer.shadowOffset = CGSize(width: 0, height: 3) + thumbView.layer.shadowPath = UIBezierPath(roundedRect: thumbView.bounds, cornerRadius: thumbView.layer.cornerRadius).cgPath + thumbView.layer.masksToBounds = false + thumbView.isUserInteractionEnabled = false + self.addSubview(thumbView) + + // thumb image + self.thumbImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: thumbView.frame.size.width, height: thumbView.frame.size.height)) + thumbImageView.contentMode = UIView.ContentMode.center + thumbImageView.autoresizingMask = UIView.AutoresizingMask.flexibleWidth + thumbView.addSubview(thumbImageView) + + self.on = false + } + + override open func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { + super.beginTracking(touch, with: event) + + startTrackingValue = self.on + didChangeWhileTracking = false + + let activeKnobWidth = self.bounds.size.height - 2 + 5 + isAnimating = true + + UIView.animate(withDuration: 0.3, delay: 0.0, options: [UIView.AnimationOptions.curveEaseOut, UIView.AnimationOptions.beginFromCurrentState], animations: { + if self.on { + self.thumbView.frame = CGRect(x: self.bounds.size.width - (activeKnobWidth + 1), y: self.thumbView.frame.origin.y, width: activeKnobWidth, height: self.thumbView.frame.size.height) + self.backgroundView.backgroundColor = self.onTintColor + self.thumbView.backgroundColor = self.onThumbTintColor + } + else { + self.thumbView.frame = CGRect(x: self.thumbView.frame.origin.x, y: self.thumbView.frame.origin.y, width: activeKnobWidth, height: self.thumbView.frame.size.height) + self.backgroundView.backgroundColor = self.activeColor + self.thumbView.backgroundColor = self.thumbTintColor + } + }, completion: { finished in + self.isAnimating = false + }) + + let shadowAnim = CABasicAnimation(keyPath: "shadowPath") + shadowAnim.duration = 0.3 + shadowAnim.fromValue = thumbView.layer.shadowPath + shadowAnim.toValue = UIBezierPath(roundedRect: thumbView.bounds, cornerRadius: thumbView.layer.cornerRadius).cgPath + thumbView.layer.add(shadowAnim, forKey: "shadowPath") + thumbView.layer.shadowPath = UIBezierPath(roundedRect: thumbView.bounds, cornerRadius: thumbView.layer.cornerRadius).cgPath + + return true + } + + override open func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { + super.continueTracking(touch, with: event) + + // Get touch location + let lastPoint = touch.location(in: self) + + // update the switch to the correct visuals depending on if + // they moved their touch to the right or left side of the switch + if lastPoint.x > self.bounds.size.width * 0.5 { + self.showOn(true) + if !startTrackingValue { + didChangeWhileTracking = true + } + } + else { + self.showOff(true) + if startTrackingValue { + didChangeWhileTracking = true + } + } + + return true + } + + override open func endTracking(_ touch: UITouch?, with event: UIEvent?) { + super.endTracking(touch, with: event) + + let previousValue = self.on + + if didChangeWhileTracking { + self.setOn(currentVisualValue, animated: true) + } + else { + self.setOn(!self.on, animated: true) + } + + if previousValue != self.on { + self.sendActions(for: UIControl.Event.valueChanged) + } + } + + override open func cancelTracking(with event: UIEvent?) { + super.cancelTracking(with: event) + + // just animate back to the original value + if self.on { + self.showOn(true) + } + else { + self.showOff(true) + } + } + + override open func layoutSubviews() { + super.layoutSubviews() + + if !isAnimating { + let frame = self.frame + + // background + backgroundView.frame = CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height) + backgroundView.layer.cornerRadius = self.isRounded ? frame.size.height * 0.5 : 2 + + // images + onImageView.frame = CGRect(x: 0, y: 0, width: frame.size.width - frame.size.height, height: frame.size.height) + offImageView.frame = CGRect(x: frame.size.height, y: 0, width: frame.size.width - frame.size.height, height: frame.size.height) + self.onLabel.frame = CGRect(x: 0, y: 0, width: frame.size.width - frame.size.height, height: frame.size.height) + self.offLabel.frame = CGRect(x: frame.size.height, y: 0, width: frame.size.width - frame.size.height, height: frame.size.height) + + // thumb + let normalKnobWidth = frame.size.height - 2 + if self.on { + thumbView.frame = CGRect(x: frame.size.width - (normalKnobWidth + 1), y: 1, width: frame.size.height - 2, height: normalKnobWidth) + thumbImageView.frame = CGRect(x: frame.size.width - normalKnobWidth, y: 0, width: normalKnobWidth, height: normalKnobWidth) + } + else { + thumbView.frame = CGRect(x: 1, y: 1, width: normalKnobWidth, height: normalKnobWidth) + thumbImageView.frame = CGRect(x: 0, y: 0, width: normalKnobWidth, height: normalKnobWidth) + } + + thumbView.layer.cornerRadius = self.isRounded ? (frame.size.height * 0.5) - 1 : 2 + thumbView.layer.shadowPath = UIBezierPath(roundedRect: thumbView.bounds, cornerRadius: thumbView.layer.cornerRadius).cgPath + } + } + + /* + * Set the state of the switch to on or off, optionally animating the transition. + */ + open func setOn(_ isOn: Bool, animated: Bool) { + switchValue = isOn + + if on { + self.showOn(animated) + } + else { + self.showOff(animated) + } + } + + /* + * Detects whether the switch is on or off + * + * @return BOOL YES if switch is on. NO if switch is off + */ + open func isOn() -> Bool { + return self.on + } + + /* + * update the looks of the switch to be in the on position + * optionally make it animated + */ + fileprivate func showOn(_ animated: Bool) { + let normalKnobWidth = self.bounds.size.height - 2 + let activeKnobWidth = normalKnobWidth + 5 + if animated { + isAnimating = true + UIView.animate(withDuration: 0.3, delay: 0.0, options: [UIView.AnimationOptions.curveEaseOut, UIView.AnimationOptions.beginFromCurrentState], animations: { + if self.isTracking { + self.thumbView.frame = CGRect(x: self.bounds.size.width - (activeKnobWidth + 1), y: self.thumbView.frame.origin.y, width: activeKnobWidth, height: self.thumbView.frame.size.height) + } + else { + self.thumbView.frame = CGRect(x: self.bounds.size.width - (normalKnobWidth + 1), y: self.thumbView.frame.origin.y, width: normalKnobWidth, height: self.thumbView.frame.size.height) + } + + self.backgroundView.backgroundColor = self.onTintColor + self.backgroundView.layer.borderColor = self.onTintColor.cgColor + self.thumbView.backgroundColor = self.onThumbTintColor + self.onImageView.alpha = 1.0 + self.offImageView.alpha = 0 + self.onLabel.alpha = 1.0 + self.offLabel.alpha = 0 + }, completion: { finished in + self.isAnimating = false + }) + + let shadowAnim = CABasicAnimation(keyPath: "shadowPath") + shadowAnim.duration = 0.3 + shadowAnim.fromValue = thumbView.layer.shadowPath + shadowAnim.toValue = UIBezierPath(roundedRect: thumbView.bounds, cornerRadius: thumbView.layer.cornerRadius).cgPath + thumbView.layer.add(shadowAnim, forKey: "shadowPath") + thumbView.layer.shadowPath = UIBezierPath(roundedRect: thumbView.bounds, cornerRadius: thumbView.layer.cornerRadius).cgPath + } + else { + if self.isTracking { + thumbView.frame = CGRect(x: self.bounds.size.width - (activeKnobWidth + 1), y: thumbView.frame.origin.y, width: activeKnobWidth, height: thumbView.frame.size.height) + } + else { + thumbView.frame = CGRect(x: self.bounds.size.width - (normalKnobWidth + 1), y: thumbView.frame.origin.y, width: normalKnobWidth, height: thumbView.frame.size.height) + } + + backgroundView.backgroundColor = self.onTintColor + backgroundView.layer.borderColor = self.onTintColor.cgColor + thumbView.backgroundColor = self.onThumbTintColor + onImageView.alpha = 1.0 + offImageView.alpha = 0 + onLabel.alpha = 1.0 + offLabel.alpha = 0 + } + + currentVisualValue = true + } + + /* + * update the looks of the switch to be in the off position + * optionally make it animated + */ + fileprivate func showOff(_ animated: Bool) { + let normalKnobWidth = self.bounds.size.height - 2 + let activeKnobWidth = normalKnobWidth + 5 + + if animated { + isAnimating = true + UIView.animate(withDuration: 0.3, delay: 0.0, options: [UIView.AnimationOptions.curveEaseOut, UIView.AnimationOptions.beginFromCurrentState], animations: { + if self.isTracking { + self.thumbView.frame = CGRect(x: 1, y: self.thumbView.frame.origin.y, width: activeKnobWidth, height: self.thumbView.frame.size.height); + self.backgroundView.backgroundColor = self.activeColor + } + else { + self.thumbView.frame = CGRect(x: 1, y: self.thumbView.frame.origin.y, width: normalKnobWidth, height: self.thumbView.frame.size.height); + self.backgroundView.backgroundColor = self.inactiveColor + } + + self.backgroundView.layer.borderColor = self.bordersColor.cgColor + self.thumbView.backgroundColor = self.thumbTintColor + self.onImageView.alpha = 0 + self.offImageView.alpha = 1.0 + self.onLabel.alpha = 0 + self.offLabel.alpha = 1.0 + + }, completion: { finished in + self.isAnimating = false + }) + + let shadowAnim = CABasicAnimation(keyPath: "shadowPath") + shadowAnim.duration = 0.3 + shadowAnim.fromValue = thumbView.layer.shadowPath + shadowAnim.toValue = UIBezierPath(roundedRect: thumbView.bounds, cornerRadius: thumbView.layer.cornerRadius).cgPath + thumbView.layer.add(shadowAnim, forKey: "shadowPath") + thumbView.layer.shadowPath = UIBezierPath(roundedRect: thumbView.bounds, cornerRadius: thumbView.layer.cornerRadius).cgPath + } + else { + if (self.isTracking) { + thumbView.frame = CGRect(x: 1, y: thumbView.frame.origin.y, width: activeKnobWidth, height: thumbView.frame.size.height) + backgroundView.backgroundColor = self.activeColor + } + else { + thumbView.frame = CGRect(x: 1, y: thumbView.frame.origin.y, width: normalKnobWidth, height: thumbView.frame.size.height) + backgroundView.backgroundColor = self.inactiveColor + } + backgroundView.layer.borderColor = self.bordersColor.cgColor + thumbView.backgroundColor = self.thumbTintColor + onImageView.alpha = 0 + offImageView.alpha = 1.0 + onLabel.alpha = 0 + offLabel.alpha = 1.0 + } + + currentVisualValue = false + } + + +} diff --git a/Visual_Novel_iOS/Src/Utils/Extensions/UIDeviceExt.swift b/Visual_Novel_iOS/Src/Utils/Extensions/UIDeviceExt.swift index bb9d645..d825fa3 100755 --- a/Visual_Novel_iOS/Src/Utils/Extensions/UIDeviceExt.swift +++ b/Visual_Novel_iOS/Src/Utils/Extensions/UIDeviceExt.swift @@ -49,7 +49,7 @@ extension UIDevice { } var hasNotch: Bool { - if #available(iOS 15.0, *) { + if #available(iOS 13.0, *) { // Use UIWindowScene.windows for iOS 15 and later let windowScene = UIApplication.shared.connectedScenes .compactMap { $0 as? UIWindowScene } @@ -61,6 +61,19 @@ extension UIDevice { } } + var safeBottom: CGFloat { + if #available(iOS 13.0, *) { + // Use UIWindowScene.windows for iOS 15 and later + let windowScene = UIApplication.shared.connectedScenes + .compactMap { $0 as? UIWindowScene } + .first + return windowScene?.windows.first?.safeAreaInsets.bottom ?? 0.0 + } else { + // Fallback for iOS versions before 15.0 + return UIApplication.shared.windows.first?.safeAreaInsets.bottom ?? 0.0 + } + } + // 状态栏 高度 var statusBarHeight: CGFloat { if #available(iOS 13.0, *) {