├── readme
└── presentation.gif
├── SwiftUI-WeChatDemo
├── Resources
│ ├── Color.xcassets
│ │ ├── Contents.json
│ │ ├── Me
│ │ │ ├── Contents.json
│ │ │ ├── FavoritesGradient
│ │ │ │ ├── Contents.json
│ │ │ │ ├── favorites.blue.colorset
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── favorites.red.colorset
│ │ │ │ │ └── Contents.json
│ │ │ │ └── favorites.yellow.colorset
│ │ │ │ │ └── Contents.json
│ │ │ ├── CardsBlue.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── PayGreen.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── SettingsBlue.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── MeMomentsBlue.colorset
│ │ │ │ └── Contents.json
│ │ │ └── StickerYellow.colorset
│ │ │ │ └── Contents.json
│ │ ├── Discover
│ │ │ ├── Contents.json
│ │ │ ├── MomentsGradient
│ │ │ │ ├── Contents.json
│ │ │ │ ├── moments.blue.colorset
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── moments.green.colorset
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── moments.red.colorset
│ │ │ │ │ └── Contents.json
│ │ │ │ └── moments.yellow.colorset
│ │ │ │ │ └── Contents.json
│ │ │ ├── LiveRed.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── NearbyBlue.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── ScanBlue.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── SearchRed.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── ShakeBlue.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── ChannelsOrange.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── TopStoriesYellow.colorset
│ │ │ │ └── Contents.json
│ │ │ └── MiniProgramsPurple.colorset
│ │ │ │ └── Contents.json
│ │ ├── ChatGray.colorset
│ │ │ └── Contents.json
│ │ ├── ChatGreen.colorset
│ │ │ └── Contents.json
│ │ ├── LightGray.colorset
│ │ │ └── Contents.json
│ │ ├── SearchGray.colorset
│ │ │ └── Contents.json
│ │ ├── TabbarGray.colorset
│ │ │ └── Contents.json
│ │ ├── TopBarGray.colorset
│ │ │ └── Contents.json
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ ├── BackgroudGray.colorset
│ │ │ └── Contents.json
│ │ └── SearchBarBackgroundGray.colorset
│ │ │ └── Contents.json
│ ├── Image.xcassets
│ │ ├── Contents.json
│ │ ├── Me
│ │ │ ├── Contents.json
│ │ │ ├── myprofile.qrcode.imageset
│ │ │ │ ├── IMG_3884.jpeg
│ │ │ │ ├── IMG_3899.jpeg
│ │ │ │ ├── IMG_3884-1.jpeg
│ │ │ │ └── Contents.json
│ │ │ ├── me.cards.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── uf_pay.svg
│ │ │ ├── me.channels.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── play.svg
│ │ │ ├── me.favorites.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── box-f.svg
│ │ │ ├── me.moments.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── image.svg
│ │ │ ├── me.pay.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── wechat-pay.svg
│ │ │ ├── me.settings.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── Gear-2.svg
│ │ │ ├── me.sticker.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── smile.svg
│ │ │ └── myprofile.qrcode.icon.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── qr-code.svg
│ │ ├── avatar
│ │ │ ├── Contents.json
│ │ │ ├── avatar_me.imageset
│ │ │ │ ├── IMG_8372_feet.jpg
│ │ │ │ └── Contents.json
│ │ │ ├── avatar01.imageset
│ │ │ │ ├── 5b250effec2628ba9620e4286f3bc67e.jpg
│ │ │ │ └── Contents.json
│ │ │ ├── avatar02.imageset
│ │ │ │ ├── 8bab2fc8ec9acd8a18d4e199f924dc69.jpg
│ │ │ │ └── Contents.json
│ │ │ ├── avatar03.imageset
│ │ │ │ ├── 4804b02102e1b2b7e3ac9ad8356c3b25.jpg
│ │ │ │ └── Contents.json
│ │ │ ├── avatar04.imageset
│ │ │ │ ├── aa02db97618aab351516b2b4cbf2abdb.jpg
│ │ │ │ └── Contents.json
│ │ │ ├── avatar05.imageset
│ │ │ │ ├── edcf8932a5c7cbb1beb829a0e0f5a584.jpg
│ │ │ │ └── Contents.json
│ │ │ ├── avatar06.imageset
│ │ │ │ ├── 8eb4993993b20c6c10d0aa025cf2be92.jpg
│ │ │ │ └── Contents.json
│ │ │ ├── avatar07.imageset
│ │ │ │ ├── 5aa8dfe1a44900201c9b609760b0919d.jpg
│ │ │ │ └── Contents.json
│ │ │ ├── avatar09.imageset
│ │ │ │ ├── b92af60f3777dbcd468dce8a412e2ee3.jpg
│ │ │ │ └── Contents.json
│ │ │ ├── avatar10.imageset
│ │ │ │ ├── a852a0b611108e9fcf588a536212b389.jpg
│ │ │ │ └── Contents.json
│ │ │ └── avatar08.imageset
│ │ │ │ ├── v2-c6af5dfc30aedca77e846825b021ee7f_1440w.png
│ │ │ │ └── Contents.json
│ │ ├── discover
│ │ │ ├── Contents.json
│ │ │ ├── discover.moments.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── shutter.svg
│ │ │ ├── discover.scan.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── hand-rock.svg
│ │ │ ├── discover.shake.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── shake-it.svg
│ │ │ ├── discover.channels.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── butterfly_f.svg
│ │ │ ├── discover.live.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── round-album-24px.svg
│ │ │ ├── discover.search.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── ic_fireworks.svg
│ │ │ ├── discover.topStories.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── md-flower.svg
│ │ │ ├── discover.miniPrograms.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── science-dna.svg
│ │ │ └── discover.nearby.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── person_circle_fill.svg
│ │ ├── AppIcon.appiconset
│ │ │ ├── icon-29.png
│ │ │ ├── icon-40.png
│ │ │ ├── icon-76.png
│ │ │ ├── icon-1024.png
│ │ │ ├── icon-20-ipad.png
│ │ │ ├── icon-20@2x.png
│ │ │ ├── icon-20@3x.png
│ │ │ ├── icon-29-ipad.png
│ │ │ ├── icon-29@2x.png
│ │ │ ├── icon-29@3x.png
│ │ │ ├── icon-40@2x.png
│ │ │ ├── icon-40@3x.png
│ │ │ ├── icon-60@2x.png
│ │ │ ├── icon-60@3x.png
│ │ │ ├── icon-76@2x.png
│ │ │ ├── icon-83.5@2x.png
│ │ │ ├── icon-20@2x-ipad.png
│ │ │ ├── icon-29@2x-ipad.png
│ │ │ └── Contents.json
│ │ └── Splash.imageset
│ │ │ ├── wp5576810@3x.png
│ │ │ └── Contents.json
│ ├── en.lproj
│ │ ├── Contact.strings
│ │ ├── LastMessage.strings
│ │ └── Localizable.strings
│ └── zh-Hans.lproj
│ │ ├── Contact.strings
│ │ ├── LastMessage.strings
│ │ └── Localizable.strings
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
├── en.lproj
│ └── InfoPlist.strings
├── zh-Hans.lproj
│ └── InfoPlist.strings
├── Data
│ ├── MyProfile.swift
│ ├── Language.swift
│ ├── ContactList.swift
│ └── ChatList.swift
├── WeChatDemoApp.swift
├── UI
│ ├── Common
│ │ ├── extension
│ │ │ └── View.swift
│ │ ├── SearchBarView.swift
│ │ └── ItemBarView.swift
│ ├── Chats
│ │ ├── Detail
│ │ │ ├── BindingModel
│ │ │ │ ├── ChatMessage.swift
│ │ │ │ └── KeyboardVisibilityReporter.swift
│ │ │ ├── ChatInputView.swift
│ │ │ ├── ChatDetailView.swift
│ │ │ ├── ChatFlowView.swift
│ │ │ ├── ViewModel
│ │ │ │ └── ChatDetailViewModel.swift
│ │ │ └── ChatMessageView.swift
│ │ ├── ChatsView.swift
│ │ └── ChatItemView.swift
│ ├── Me
│ │ ├── LanguageSwitcher.swift
│ │ ├── QrCodeView.swift
│ │ ├── MeView.swift
│ │ ├── MyProfileView.swift
│ │ └── Data
│ │ │ └── MeItems.swift
│ ├── Contacts
│ │ ├── ContactItemView.swift
│ │ └── ContactsView.swift
│ ├── Discover
│ │ ├── DiscoverView.swift
│ │ └── Data
│ │ │ └── DiscoverItems.swift
│ └── TabContainer.swift
├── Info.plist
└── Generated
│ ├── stringsExtra.swift
│ ├── strings.swift
│ └── assets.swift
├── SwiftUI-WeChatDemo.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── xcuserdata
│ └── wavky.xcuserdatad
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
└── xcshareddata
│ └── xcschemes
│ └── SwiftUI-WeChatDemo.xcscheme
├── swiftgen.yml
├── README.md
├── .gitignore
├── swiftui-strings-template.stencil
├── swiftui-assets-template.stencil
└── LICENSE
/readme/presentation.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wavky/SwiftUI-WeChatDemo/HEAD/readme/presentation.gif
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Color.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Color.xcassets/Me/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/Me/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Color.xcassets/Discover/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/avatar/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/discover/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Color.xcassets/Me/FavoritesGradient/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Color.xcassets/Discover/MomentsGradient/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/AppIcon.appiconset/icon-29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wavky/SwiftUI-WeChatDemo/HEAD/SwiftUI-WeChatDemo/Resources/Image.xcassets/AppIcon.appiconset/icon-29.png
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/AppIcon.appiconset/icon-40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wavky/SwiftUI-WeChatDemo/HEAD/SwiftUI-WeChatDemo/Resources/Image.xcassets/AppIcon.appiconset/icon-40.png
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/AppIcon.appiconset/icon-76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wavky/SwiftUI-WeChatDemo/HEAD/SwiftUI-WeChatDemo/Resources/Image.xcassets/AppIcon.appiconset/icon-76.png
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/AppIcon.appiconset/icon-1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wavky/SwiftUI-WeChatDemo/HEAD/SwiftUI-WeChatDemo/Resources/Image.xcassets/AppIcon.appiconset/icon-1024.png
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/Splash.imageset/wp5576810@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wavky/SwiftUI-WeChatDemo/HEAD/SwiftUI-WeChatDemo/Resources/Image.xcassets/Splash.imageset/wp5576810@3x.png
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/AppIcon.appiconset/icon-20-ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wavky/SwiftUI-WeChatDemo/HEAD/SwiftUI-WeChatDemo/Resources/Image.xcassets/AppIcon.appiconset/icon-20-ipad.png
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/AppIcon.appiconset/icon-20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wavky/SwiftUI-WeChatDemo/HEAD/SwiftUI-WeChatDemo/Resources/Image.xcassets/AppIcon.appiconset/icon-20@2x.png
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/AppIcon.appiconset/icon-20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wavky/SwiftUI-WeChatDemo/HEAD/SwiftUI-WeChatDemo/Resources/Image.xcassets/AppIcon.appiconset/icon-20@3x.png
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/AppIcon.appiconset/icon-29-ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wavky/SwiftUI-WeChatDemo/HEAD/SwiftUI-WeChatDemo/Resources/Image.xcassets/AppIcon.appiconset/icon-29-ipad.png
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/AppIcon.appiconset/icon-29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wavky/SwiftUI-WeChatDemo/HEAD/SwiftUI-WeChatDemo/Resources/Image.xcassets/AppIcon.appiconset/icon-29@2x.png
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/AppIcon.appiconset/icon-29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wavky/SwiftUI-WeChatDemo/HEAD/SwiftUI-WeChatDemo/Resources/Image.xcassets/AppIcon.appiconset/icon-29@3x.png
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/AppIcon.appiconset/icon-40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wavky/SwiftUI-WeChatDemo/HEAD/SwiftUI-WeChatDemo/Resources/Image.xcassets/AppIcon.appiconset/icon-40@2x.png
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/AppIcon.appiconset/icon-40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wavky/SwiftUI-WeChatDemo/HEAD/SwiftUI-WeChatDemo/Resources/Image.xcassets/AppIcon.appiconset/icon-40@3x.png
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/AppIcon.appiconset/icon-60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wavky/SwiftUI-WeChatDemo/HEAD/SwiftUI-WeChatDemo/Resources/Image.xcassets/AppIcon.appiconset/icon-60@2x.png
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/AppIcon.appiconset/icon-60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wavky/SwiftUI-WeChatDemo/HEAD/SwiftUI-WeChatDemo/Resources/Image.xcassets/AppIcon.appiconset/icon-60@3x.png
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/AppIcon.appiconset/icon-76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wavky/SwiftUI-WeChatDemo/HEAD/SwiftUI-WeChatDemo/Resources/Image.xcassets/AppIcon.appiconset/icon-76@2x.png
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/AppIcon.appiconset/icon-83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wavky/SwiftUI-WeChatDemo/HEAD/SwiftUI-WeChatDemo/Resources/Image.xcassets/AppIcon.appiconset/icon-83.5@2x.png
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/en.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /*
2 | InfoPlist.strings
3 | SwiftUI-WeChatDemo
4 |
5 | Created by Wavky Huang on 2021/07/14.
6 |
7 | */
8 |
9 | CFBundleDisplayName = "WeChatDemo";
10 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/zh-Hans.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /*
2 | InfoPlist.strings
3 | SwiftUI-WeChatDemo
4 |
5 | Created by Wavky Huang on 2021/07/14.
6 |
7 | */
8 |
9 | CFBundleDisplayName = "微信 Demo";
10 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/AppIcon.appiconset/icon-20@2x-ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wavky/SwiftUI-WeChatDemo/HEAD/SwiftUI-WeChatDemo/Resources/Image.xcassets/AppIcon.appiconset/icon-20@2x-ipad.png
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/AppIcon.appiconset/icon-29@2x-ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wavky/SwiftUI-WeChatDemo/HEAD/SwiftUI-WeChatDemo/Resources/Image.xcassets/AppIcon.appiconset/icon-29@2x-ipad.png
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/Me/myprofile.qrcode.imageset/IMG_3884.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wavky/SwiftUI-WeChatDemo/HEAD/SwiftUI-WeChatDemo/Resources/Image.xcassets/Me/myprofile.qrcode.imageset/IMG_3884.jpeg
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/Me/myprofile.qrcode.imageset/IMG_3899.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wavky/SwiftUI-WeChatDemo/HEAD/SwiftUI-WeChatDemo/Resources/Image.xcassets/Me/myprofile.qrcode.imageset/IMG_3899.jpeg
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/Me/myprofile.qrcode.imageset/IMG_3884-1.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wavky/SwiftUI-WeChatDemo/HEAD/SwiftUI-WeChatDemo/Resources/Image.xcassets/Me/myprofile.qrcode.imageset/IMG_3884-1.jpeg
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/avatar/avatar_me.imageset/IMG_8372_feet.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wavky/SwiftUI-WeChatDemo/HEAD/SwiftUI-WeChatDemo/Resources/Image.xcassets/avatar/avatar_me.imageset/IMG_8372_feet.jpg
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/avatar/avatar01.imageset/5b250effec2628ba9620e4286f3bc67e.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wavky/SwiftUI-WeChatDemo/HEAD/SwiftUI-WeChatDemo/Resources/Image.xcassets/avatar/avatar01.imageset/5b250effec2628ba9620e4286f3bc67e.jpg
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/avatar/avatar02.imageset/8bab2fc8ec9acd8a18d4e199f924dc69.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wavky/SwiftUI-WeChatDemo/HEAD/SwiftUI-WeChatDemo/Resources/Image.xcassets/avatar/avatar02.imageset/8bab2fc8ec9acd8a18d4e199f924dc69.jpg
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/avatar/avatar03.imageset/4804b02102e1b2b7e3ac9ad8356c3b25.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wavky/SwiftUI-WeChatDemo/HEAD/SwiftUI-WeChatDemo/Resources/Image.xcassets/avatar/avatar03.imageset/4804b02102e1b2b7e3ac9ad8356c3b25.jpg
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/avatar/avatar04.imageset/aa02db97618aab351516b2b4cbf2abdb.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wavky/SwiftUI-WeChatDemo/HEAD/SwiftUI-WeChatDemo/Resources/Image.xcassets/avatar/avatar04.imageset/aa02db97618aab351516b2b4cbf2abdb.jpg
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/avatar/avatar05.imageset/edcf8932a5c7cbb1beb829a0e0f5a584.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wavky/SwiftUI-WeChatDemo/HEAD/SwiftUI-WeChatDemo/Resources/Image.xcassets/avatar/avatar05.imageset/edcf8932a5c7cbb1beb829a0e0f5a584.jpg
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/avatar/avatar06.imageset/8eb4993993b20c6c10d0aa025cf2be92.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wavky/SwiftUI-WeChatDemo/HEAD/SwiftUI-WeChatDemo/Resources/Image.xcassets/avatar/avatar06.imageset/8eb4993993b20c6c10d0aa025cf2be92.jpg
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/avatar/avatar07.imageset/5aa8dfe1a44900201c9b609760b0919d.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wavky/SwiftUI-WeChatDemo/HEAD/SwiftUI-WeChatDemo/Resources/Image.xcassets/avatar/avatar07.imageset/5aa8dfe1a44900201c9b609760b0919d.jpg
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/avatar/avatar09.imageset/b92af60f3777dbcd468dce8a412e2ee3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wavky/SwiftUI-WeChatDemo/HEAD/SwiftUI-WeChatDemo/Resources/Image.xcassets/avatar/avatar09.imageset/b92af60f3777dbcd468dce8a412e2ee3.jpg
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/avatar/avatar10.imageset/a852a0b611108e9fcf588a536212b389.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wavky/SwiftUI-WeChatDemo/HEAD/SwiftUI-WeChatDemo/Resources/Image.xcassets/avatar/avatar10.imageset/a852a0b611108e9fcf588a536212b389.jpg
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/avatar/avatar08.imageset/v2-c6af5dfc30aedca77e846825b021ee7f_1440w.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wavky/SwiftUI-WeChatDemo/HEAD/SwiftUI-WeChatDemo/Resources/Image.xcassets/avatar/avatar08.imageset/v2-c6af5dfc30aedca77e846825b021ee7f_1440w.png
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/Me/me.cards.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "uf_pay.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/Me/me.channels.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "play.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/Me/me.favorites.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "box-f.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/Me/me.moments.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "image.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/Me/me.pay.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "wechat-pay.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/Me/me.settings.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Gear-2.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/Me/me.sticker.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "smile.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/Splash.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "wp5576810@3x.png",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/Me/myprofile.qrcode.icon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "qr-code.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/discover/discover.moments.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "shutter.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/discover/discover.scan.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "hand-rock.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/discover/discover.shake.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "shake-it.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/discover/discover.channels.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "butterfly_f.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/discover/discover.live.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "round-album-24px.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/discover/discover.search.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "ic_fireworks.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/discover/discover.topStories.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "md-flower.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/discover/discover.miniPrograms.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "science-dna.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/discover/discover.nearby.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "person_circle_fill.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/en.lproj/Contact.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Contact.strings
3 | SwiftUI-WeChatDemo
4 |
5 | Created by Wavky Huang on 2021/07/18.
6 |
7 | */
8 |
9 | Name.Antony = "Antony";
10 | Name.Apple = "Apple";
11 | Name.Bosco = "Bosco";
12 | Name.Joe = "Joe";
13 | Name.Microsoft = "Microsoft";
14 | Name.Muraosa = "Muraosa";
15 | Name.Ron = "Ron";
16 | Name.Saha = "Saha";
17 | Name.Sony = "Sony";
18 | Name.UncleWang = "UncleWang";
19 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/zh-Hans.lproj/Contact.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Contact.strings
3 | SwiftUI-WeChatDemo
4 |
5 | Created by Wavky Huang on 2021/07/18.
6 |
7 | */
8 |
9 | Name.Antony = "安东尼🧑🏻💻";
10 | Name.Apple = "小苹果🍎";
11 | Name.Bosco = "波💪🏽士";
12 | Name.Joe = "阿祖";
13 | Name.Microsoft = "巨硬";
14 | Name.Muraosa = "💋村长💋";
15 | Name.Ron = "Ron";
16 | Name.Saha = "沙哈😈";
17 | Name.Sony = "索尼大法";
18 | Name.UncleWang = "你的好邻居王叔叔";
19 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Data/MyProfile.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MyProfile.swift
3 | // SwiftUI-WeChatDemo
4 | //
5 | // Created by Wavky Huang on 2021/07/17.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | struct MyProfile {
12 | let profileImage: String = Asset.Image.avatarMe.name
13 | let qrcodeImage: String = Asset.Image.myprofileQrcode.name
14 | let name: LocalizedStringKey = L10n.MyProfile.name.key
15 | let weChatID: String = "Wavky_Huang"
16 | }
17 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/avatar/avatar_me.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "IMG_8372_feet.jpg",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Data/Language.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Language.swift
3 | // SwiftUI-WeChatDemo
4 | //
5 | // Created by Wavky Huang on 2021/07/19.
6 | //
7 |
8 | import Foundation
9 |
10 | enum Language: String, CaseIterable {
11 | case English = "English", Chinese = "中文"
12 |
13 | var code: String {
14 | switch self {
15 | case .English:
16 | return "en"
17 | case .Chinese:
18 | return "zh-Hans"
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Color.xcassets/ChatGray.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.929",
9 | "green" : "0.929",
10 | "red" : "0.929"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Color.xcassets/ChatGreen.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.474",
9 | "green" : "0.915",
10 | "red" : "0.658"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Color.xcassets/LightGray.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.796",
9 | "green" : "0.796",
10 | "red" : "0.796"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Color.xcassets/SearchGray.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.706",
9 | "green" : "0.706",
10 | "red" : "0.702"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Color.xcassets/TabbarGray.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xF7",
9 | "green" : "0xF6",
10 | "red" : "0xF7"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Color.xcassets/TopBarGray.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.969",
9 | "green" : "0.969",
10 | "red" : "0.969"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Color.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.416",
9 | "green" : "0.745",
10 | "red" : "0.341"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Color.xcassets/BackgroudGray.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.929",
9 | "green" : "0.929",
10 | "red" : "0.929"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Color.xcassets/Me/CardsBlue.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.914",
9 | "green" : "0.426",
10 | "red" : "0.030"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Color.xcassets/Me/PayGreen.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.404",
9 | "green" : "0.737",
10 | "red" : "0.224"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Color.xcassets/Me/SettingsBlue.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.898",
9 | "green" : "0.467",
10 | "red" : "0.029"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/avatar/avatar01.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "5b250effec2628ba9620e4286f3bc67e.jpg",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/avatar/avatar02.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "8bab2fc8ec9acd8a18d4e199f924dc69.jpg",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/avatar/avatar03.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "4804b02102e1b2b7e3ac9ad8356c3b25.jpg",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/avatar/avatar04.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "aa02db97618aab351516b2b4cbf2abdb.jpg",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/avatar/avatar05.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "edcf8932a5c7cbb1beb829a0e0f5a584.jpg",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/avatar/avatar06.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "8eb4993993b20c6c10d0aa025cf2be92.jpg",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/avatar/avatar07.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "5aa8dfe1a44900201c9b609760b0919d.jpg",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/avatar/avatar09.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "b92af60f3777dbcd468dce8a412e2ee3.jpg",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/avatar/avatar10.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "a852a0b611108e9fcf588a536212b389.jpg",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Color.xcassets/Discover/LiveRed.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.263",
9 | "green" : "0.386",
10 | "red" : "0.928"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Color.xcassets/Discover/NearbyBlue.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.886",
9 | "green" : "0.414",
10 | "red" : "0.028"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Color.xcassets/Discover/ScanBlue.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.894",
9 | "green" : "0.410",
10 | "red" : "0.029"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Color.xcassets/Discover/SearchRed.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.251",
9 | "green" : "0.288",
10 | "red" : "0.897"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Color.xcassets/Discover/ShakeBlue.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.902",
9 | "green" : "0.529",
10 | "red" : "0.279"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Color.xcassets/Me/MeMomentsBlue.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.894",
9 | "green" : "0.451",
10 | "red" : "0.029"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Color.xcassets/Me/StickerYellow.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.000",
9 | "green" : "0.747",
10 | "red" : "0.960"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Color.xcassets/Discover/ChannelsOrange.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.247",
9 | "green" : "0.608",
10 | "red" : "0.924"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Color.xcassets/Discover/TopStoriesYellow.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.266",
9 | "green" : "0.770",
10 | "red" : "0.960"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Color.xcassets/SearchBarBackgroundGray.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.898",
9 | "green" : "0.898",
10 | "red" : "0.898"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/avatar/avatar08.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "v2-c6af5dfc30aedca77e846825b021ee7f_1440w.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Color.xcassets/Discover/MiniProgramsPurple.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.898",
9 | "green" : "0.234",
10 | "red" : "0.214"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Color.xcassets/Me/FavoritesGradient/favorites.blue.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.914",
9 | "green" : "0.491",
10 | "red" : "0.086"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Color.xcassets/Me/FavoritesGradient/favorites.red.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.125",
9 | "green" : "0.167",
10 | "red" : "0.881"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Color.xcassets/Discover/MomentsGradient/moments.blue.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.902",
9 | "green" : "0.300",
10 | "red" : "0.283"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Color.xcassets/Discover/MomentsGradient/moments.green.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.404",
9 | "green" : "0.741",
10 | "red" : "0.127"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Color.xcassets/Discover/MomentsGradient/moments.red.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.145",
9 | "green" : "0.202",
10 | "red" : "0.893"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Color.xcassets/Discover/MomentsGradient/moments.yellow.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.263",
9 | "green" : "0.774",
10 | "red" : "0.964"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Color.xcassets/Me/FavoritesGradient/favorites.yellow.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.235",
9 | "green" : "0.754",
10 | "red" : "0.960"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/WeChatDemoApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftUI_WeChatDemoApp.swift
3 | // SwiftUI-WeChatDemo
4 | //
5 | // Created by Wavky Huang on 2021/07/14.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @main
11 | struct WeChatDemoApp: App {
12 | @AppStorage("AppLanguage")
13 | private var appLanguage: String = Language.English.code
14 |
15 | var body: some Scene {
16 | WindowGroup {
17 | TabContainer()
18 | .environment(\.locale,
19 | .init(identifier: appLanguage))
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/zh-Hans.lproj/LastMessage.strings:
--------------------------------------------------------------------------------
1 | /*
2 | LastMessage.strings
3 | SwiftUI-WeChatDemo
4 |
5 | Created by Wavky Huang on 2021/07/18.
6 |
7 | */
8 |
9 | Message.Hi = "嗨";
10 | Message.Hey = "嘿";
11 | Message.Hello = "哈喽";
12 | Message.NiceToMeetYou = "很高兴认识你";
13 | Message.HowAreYou = "怎么是你?";
14 | Message.Aloha = "一袋米要扛几楼";
15 | Message.AreYouStillThere = "你还活着吗?";
16 | Message.ILoveYou = "么么哒😘";
17 | Message.TheMatrixIsLookingForYou = "妈妈喊你回家吃饭";
18 | Message.IAmRobot = "我是这条街最靓的仔😎";
19 |
20 | DateTime.Yesterday = "昨天";
21 | DateTime.Monday = "星期一";
22 | DateTime.Sunday = "星期日";
23 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo.xcodeproj/xcuserdata/wavky.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | SwiftUI-WeChatDemo.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 |
13 | SuppressBuildableAutocreation
14 |
15 | C62D9DDB269DE750003F9923
16 |
17 | primary
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/en.lproj/LastMessage.strings:
--------------------------------------------------------------------------------
1 | /*
2 | LastMessage.strings
3 | SwiftUI-WeChatDemo
4 |
5 | Created by Wavky Huang on 2021/07/18.
6 |
7 | */
8 |
9 | Message.Hi = "Hi";
10 | Message.Hey = "Hey";
11 | Message.Hello = "Hello";
12 | Message.NiceToMeetYou = "Nice to meet you~";
13 | Message.HowAreYou = "How are you?";
14 | Message.Aloha = "Aloha";
15 | Message.AreYouStillThere = "Are you still there?";
16 | Message.ILoveYou = "I love you!";
17 | Message.TheMatrixIsLookingForYou = "The Matrix is looking for you.";
18 | Message.IAmRobot = "I am robot.";
19 |
20 | DateTime.Yesterday = "Yesterday";
21 | DateTime.Monday = "Monday";
22 | DateTime.Sunday = "Sunday";
23 |
--------------------------------------------------------------------------------
/swiftgen.yml:
--------------------------------------------------------------------------------
1 | input_dir: ${TARGET_NAME}/Resources/
2 | output_dir: ${TARGET_NAME}/Generated/
3 | strings:
4 | - inputs: en.lproj/Localizable.strings
5 | outputs:
6 | - templatePath: swiftui-strings-template.stencil
7 | output: strings.swift
8 | - inputs: en.lproj/
9 | filter: ^((?!InfoPlist|Localizable).)+\.strings$
10 | outputs:
11 | - templatePath: swiftui-strings-template.stencil
12 | output: stringsExtra.swift
13 | params:
14 | enumName: L10nExtra
15 | withoutSupporter:
16 | xcassets:
17 | inputs: .
18 | filter: .+\.xcassets$
19 | outputs:
20 | - templatePath: swiftui-assets-template.stencil
21 | output: assets.swift
22 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/UI/Common/extension/View.swift:
--------------------------------------------------------------------------------
1 | //
2 | // View.swift
3 | // SwiftUI-WeChatDemo
4 | //
5 | // Created by Wavky Huang on 2021/07/18.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | extension View {
12 | func withBadge(_ show: Bool, radius: CGFloat = 6) -> some View {
13 | if show {
14 | return AnyView(self.overlay(Circle()
15 | .fill(Color.red)
16 | .frame(width: radius * 2, height: radius * 2)
17 | .offset(x: radius, y: -radius),
18 | alignment: .topTrailing))
19 | } else {
20 | return AnyView(self)
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/UI/Chats/Detail/BindingModel/ChatMessage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChatMessage.swift
3 | // SwiftUI-WeChatDemo
4 | //
5 | // Created by Wavky Huang on 2021/07/15.
6 | //
7 |
8 | import Foundation
9 |
10 | struct ChatMessage: Hashable {
11 | let side: Side
12 | let text: MessageType
13 | }
14 |
15 | enum MessageType: Hashable {
16 | case String(text: String),
17 | LocalizedString(text: LocalizedString)
18 | }
19 |
20 | enum Side: Hashable {
21 | case Left(profileImage: String), Right
22 |
23 | var profileImage: String {
24 | switch self {
25 | case .Left(let profileImage):
26 | return profileImage
27 | case .Right:
28 | return Asset.Image.avatarMe.name
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/Me/me.pay.imageset/wechat-pay.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/Me/me.cards.imageset/uf_pay.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/UI/Common/SearchBarView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SearchBarView.swift
3 | // SwiftUI-WeChatDemo
4 | //
5 | // Created by Wavky Huang on 2021/07/16.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SearchBarView: View {
11 | var body: some View {
12 | HStack {
13 | (Text(Image(systemName: "magnifyingglass"))+Text(" ")+Text(L10n.SearchBar.hint.key))
14 | .foregroundColor(Asset.Color.searchGray.color)
15 | .padding(8)
16 | .frame(maxWidth: .infinity)
17 | .background(Color.white)
18 | .clipShape(RoundedRectangle(cornerRadius: 4))
19 | }
20 | .padding(8)
21 | .background(Asset.Color.searchBarBackgroundGray.color)
22 | }
23 | }
24 |
25 | struct SearchBarView_Previews: PreviewProvider {
26 | static var previews: some View {
27 | SearchBarView()
28 | }
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/discover/discover.live.imageset/round-album-24px.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/Me/me.sticker.imageset/smile.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/UI/Me/LanguageSwitcher.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LanguageSwitcher.swift
3 | // SwiftUI-WeChatDemo
4 | //
5 | // Created by Wavky Huang on 2021/07/18.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct LanguageSwitcher: View {
11 | private let languages = Language.allCases
12 |
13 | @AppStorage("AppLanguage")
14 | private var appLanguage: String = Language.English.code
15 |
16 | var body: some View {
17 | VStack {
18 | Picker(selection: $appLanguage, label: EmptyView()) {
19 | ForEach(languages, id: \.code) { lang in
20 | Text(lang.rawValue)
21 | }
22 | }
23 | .pickerStyle(SegmentedPickerStyle())
24 | .padding(.horizontal, 40)
25 | }
26 | }
27 | }
28 |
29 | struct LanguageSwitcher_Previews: PreviewProvider {
30 | static var previews: some View {
31 | LanguageSwitcher()
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/zh-Hans.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | SwiftUI-WeChatDemo
4 |
5 | Created by Wavky Huang on 2021/07/14.
6 |
7 | */
8 |
9 | AppName = "微信";
10 |
11 | Tabs.Chats = "微信";
12 | Tabs.Contacts = "通讯录";
13 | Tabs.Discover = "发现";
14 | Tabs.Me = "我";
15 |
16 | SearchBar.Hint = "搜索";
17 |
18 | Discover.Moments = "朋友圈";
19 | Discover.Channels = "视频号";
20 | Discover.Live = "直播";
21 | Discover.Scan = "扫一扫";
22 | Discover.Shake = "摇一摇";
23 | Discover.TopStories = "看一看";
24 | Discover.Search = "搜一搜";
25 | Discover.Nearby = "附近";
26 | Discover.MiniPrograms = "小程序";
27 | Discover.Live.OnAir = "直播中";
28 |
29 | Me.Pay = "支付";
30 | Me.Favorites = "收藏";
31 | Me.Moments = "朋友圈";
32 | Me.Channels = "视频号";
33 | Me.Cards = "卡包";
34 | Me.Sticker = "表情";
35 | Me.Settings = "设置";
36 |
37 | MyProfile.Name = "猫猫腿";
38 | MyProfile.Status = "状态";
39 | MyProfile.WeChatID = "微信号: ";
40 |
41 | QrCode.Title = "我的二维码";
42 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/Me/me.channels.imageset/play.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/Me/me.favorites.imageset/box-f.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/Me/me.moments.imageset/image.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/discover/discover.topStories.imageset/md-flower.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/discover/discover.scan.imageset/hand-rock.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 用 SwiftUI 五天组装一个微信
2 |
3 | ## 效果图
4 | 
5 |
6 | ## 实装内容
7 | * 4 个 Tab 页面 + 聊天界面,使用纯 SwiftUI 搭建而成
8 | * 应用启动界面 Launch Screen
9 | * 国际化及应用内语言切换功能
10 | * Combine 使用场景(聊天界面简易对话功能)
11 | * 极度简化的 MVVM 架构
12 | * 逻辑部分纯 Swift5 书写,几乎不涉及 OC 类及 UIKit 库
13 | * 不使用第三方框架(除了 SwiftGen 提供便利)
14 |
15 | ## 难点
16 | * 在聊天界面中隐藏 Tabbar(未能实现)
17 | * 聊天界面中信息滚动功能,尤其动画滑动效果难以实现
18 | * 保持键盘焦点,不自动隐藏(有望在 SwiftUI 3 后实现)
19 | * 发现页图标的动态颜色指定(实现起来障碍较大,多番尝试发现无法简单地将 Color 和 一众渐变色类型 存储到单一变量,并且 View 比较难以扩展,语法糖副作用严重)
20 | * SwiftUI 原生视图难以完美复刻微信原生界面(尤其是联系人页),判断是微信使用了大量自定义视图
21 | * Tab 按钮添加红点(无法实现,SwiftUI 原生视图仅接受 Image 和 Text,对 Modifier 的修改直接忽略)
22 | * SwiftUI 中 Combine 的使用(通过 ObservableObject 构造 ViewModel 实现)
23 | * 应用内语言切换(通过 [自定义配置 SwiftGen](https://github.com/wavky/SwiftGenConfigForSwiftUI) 实现)
24 |
25 | ## 坑
26 | * SwiftUI2 中缺乏视图焦点控制方法(该部分将在 SwiftUI3 中提供)
27 | * SwiftUI 中使用 switch 语法时,会导致自动提示失效,但编译能顺利通过,判断是 Xcode 问题
28 | * 目前在书写 SwiftUI 时遇到语法使用错误等情况,偶尔会出现 Xcode 提示未知错误,建议报告 bug(因为逐行书写,基本都能猜出问题所在)
29 |
30 | ---
31 |
32 | ## 彩蛋
33 | 应用内包含微信联系方式,欢迎同行交流(本人是 Android 攻城狮..)
34 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/en.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | SwiftUI-WeChatDemo
4 |
5 | Created by Wavky Huang on 2021/07/14.
6 |
7 | */
8 |
9 | AppName = "WeChat";
10 |
11 | Tabs.Chats = "Chats";
12 | Tabs.Contacts = "Contacts";
13 | Tabs.Discover = "Discover";
14 | Tabs.Me = "Me";
15 |
16 | SearchBar.Hint = "Search";
17 |
18 | Discover.Moments = "Moments";
19 | Discover.Channels = "Channels";
20 | Discover.Live = "Live";
21 | Discover.Scan = "Scan";
22 | Discover.Shake = "Shake";
23 | Discover.TopStories = "Top Stories";
24 | Discover.Search = "Search";
25 | Discover.Nearby = "Nearby";
26 | Discover.MiniPrograms = "Mini Programs";
27 | Discover.Live.OnAir = "Live";
28 |
29 | Me.Pay = "Pay";
30 | Me.Favorites = "Favorites";
31 | Me.Moments = "Moments";
32 | Me.Channels = "Channels";
33 | Me.Cards = "Cards & Offers";
34 | Me.Sticker = "Sticker Gallery";
35 | Me.Settings = "Settings";
36 |
37 | MyProfile.Name = "Wavky Huang";
38 | MyProfile.Status = "Status";
39 | MyProfile.WeChatID = "WeChat ID: ";
40 |
41 | QrCode.Title = "My QR Code";
42 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/UI/Chats/Detail/BindingModel/KeyboardVisibilityReporter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // KeyboardVisibilityReporter.swift
3 | // SwiftUI-WeChatDemo
4 | //
5 | // Copy from https://www.youtube.com/watch?v=D8Eo0c_ciMk
6 | // Created by Wavky Huang on 2021/07/15.
7 | //
8 |
9 | import Foundation
10 | import Combine
11 | import SwiftUI
12 |
13 | class KeyboardVisibilityReporter: ObservableObject {
14 | @Published private(set) var isVisible: Bool = false
15 |
16 | private var cancelable: AnyCancellable?
17 |
18 | private let keyboardWillShow =
19 | NotificationCenter.default
20 | .publisher(for: UIResponder.keyboardDidShowNotification)
21 | .compactMap { _ in true }
22 |
23 | private let keyboardWillHide =
24 | NotificationCenter.default
25 | .publisher(for: UIResponder.keyboardDidHideNotification)
26 | .map { _ in false }
27 |
28 | init() {
29 | cancelable = Publishers.Merge(keyboardWillShow, keyboardWillHide)
30 | .subscribe(on: DispatchQueue.main)
31 | .assign(to: \.self.isVisible, on: self)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/discover/discover.nearby.imageset/person_circle_fill.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/Me/myprofile.qrcode.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "IMG_3884.jpeg",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | },
16 | {
17 | "filename" : "IMG_3899.jpeg",
18 | "idiom" : "universal",
19 | "locale" : "zh-Hans",
20 | "scale" : "1x"
21 | },
22 | {
23 | "idiom" : "universal",
24 | "locale" : "zh-Hans",
25 | "scale" : "2x"
26 | },
27 | {
28 | "idiom" : "universal",
29 | "locale" : "zh-Hans",
30 | "scale" : "3x"
31 | },
32 | {
33 | "filename" : "IMG_3884-1.jpeg",
34 | "idiom" : "universal",
35 | "locale" : "en",
36 | "scale" : "1x"
37 | },
38 | {
39 | "idiom" : "universal",
40 | "locale" : "en",
41 | "scale" : "2x"
42 | },
43 | {
44 | "idiom" : "universal",
45 | "locale" : "en",
46 | "scale" : "3x"
47 | }
48 | ],
49 | "info" : {
50 | "author" : "xcode",
51 | "version" : 1
52 | },
53 | "properties" : {
54 | "localizable" : true
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/discover/discover.search.imageset/ic_fireworks.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/discover/discover.shake.imageset/shake-it.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/UI/Me/QrCodeView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // QrCodeView.swift
3 | // SwiftUI-WeChatDemo
4 | //
5 | // Created by Wavky Huang on 2021/07/17.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct QrCodeView: View {
11 | @Environment(\.presentationMode) var presentationMode
12 |
13 | var body: some View {
14 | ZStack {
15 | Image(Asset.Image.myprofileQrcode.name)
16 | .resizable()
17 | .scaledToFit()
18 | .cornerRadius(8.0)
19 | }
20 | .padding(20)
21 | .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
22 | .background(Asset.Color.backgroudGray.color)
23 | .navigationBarTitleDisplayMode(.inline)
24 | .navigationTitle(L10n.QrCode.title.key)
25 | .navigationBarBackButtonHidden(true)
26 | .toolbar {
27 | ToolbarItem(placement: .navigationBarLeading) {
28 | Button(action: { presentationMode.wrappedValue.dismiss() }) {
29 | Image(systemName: "chevron.backward")
30 | }
31 | }
32 | ToolbarItem(placement: .navigationBarTrailing){
33 | Button(action: {}) {
34 | Image(systemName: "ellipsis")
35 | }
36 | }
37 | }
38 | }
39 | }
40 |
41 | struct QrCodeView_Previews: PreviewProvider {
42 | static var previews: some View {
43 | QrCodeView()
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/UI/Chats/Detail/ChatInputView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChatInputView.swift
3 | // SwiftUI-WeChatDemo
4 | //
5 | // Created by Wavky Huang on 2021/07/14.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ChatInputView: View {
11 | @State private var message: String = ""
12 | let onCommit: (String) -> Void
13 |
14 | var body: some View {
15 | HStack {
16 | Image(systemName: "speaker.wave.2.circle")
17 | .chatInputIconStyle()
18 | TextField("", text: $message, onCommit: {
19 | onCommit(message)
20 | message = ""
21 | })
22 | .padding(8)
23 | .accentColor(Asset.Color.accentColor.color)
24 | .background(Color.white)
25 | .clipShape(RoundedRectangle(cornerRadius: 4))
26 | .frame(maxWidth: .infinity)
27 |
28 | Image(systemName: "face.smiling")
29 | .chatInputIconStyle()
30 | Image(systemName: "plus.circle")
31 | .chatInputIconStyle()
32 | }
33 | .padding(8)
34 | .background(Asset.Color.tabbarGray.color)
35 | }
36 | }
37 |
38 | private extension Image {
39 | func chatInputIconStyle() -> some View {
40 | return self.font(.system(size: 28, weight: .light, design: .default))
41 | }
42 | }
43 |
44 | struct ChatInputView_Previews: PreviewProvider {
45 | static var previews: some View {
46 | ChatInputView(onCommit: { _ in })
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/UI/Contacts/ContactItemView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContactItemView.swift
3 | // SwiftUI-WeChatDemo
4 | //
5 | // Created by Wavky Huang on 2021/07/17.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ContactItemView: View {
11 | let contact: Contact
12 |
13 | var body: some View {
14 | HStack() {
15 | Image(contact.profileImage)
16 | .resizable()
17 | .scaledToFit()
18 | .frame(width: 40, height: 40, alignment: .center)
19 | .cornerRadius(4.0)
20 | Spacer(minLength: 15)
21 | VStack(alignment: .leading) {
22 | Spacer()
23 | Text(contact.name.key,
24 | tableName: contact.name.table)
25 | .font(.title3)
26 | .lineLimit(1)
27 | .frame(maxWidth: .infinity, alignment: .leading)
28 | Spacer()
29 | }
30 | }
31 | .padding(.trailing)
32 | .frame(height: 60, alignment: .leading)
33 | .frame(maxWidth: .infinity)
34 | }
35 | }
36 |
37 | struct ContactItemView_Previews: PreviewProvider {
38 | static let contacts = getDefaultContactList().shuffled()
39 |
40 | static var previews: some View {
41 | ScrollView {
42 | ContactItemView(contact: contacts[0])
43 | ContactItemView(contact: contacts[1])
44 | ContactItemView(contact: contacts[2])
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/UI/Chats/Detail/ChatDetailView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChatDetailView.swift
3 | // SwiftUI-WeChatDemo
4 | //
5 | // Created by Wavky Huang on 2021/07/14.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ChatDetailView: View {
11 | @State var chat: Chat
12 | @StateObject var viewModel: ChatDetailViewModel
13 | @Environment(\.presentationMode) var presentationMode
14 |
15 | var body: some View {
16 | VStack(spacing: 0) {
17 | ChatFlowView(messageFlow: $viewModel.messageFlow).onAppear() {
18 | viewModel.subscribe()
19 | viewModel.sendLastMessage(chat.lastMessage)
20 | }
21 | ChatInputView { message in
22 | viewModel.send(message)
23 | viewModel.echo(message)
24 | }
25 | }
26 | .navigationTitle(Text(chat.contact.name.key,
27 | tableName: chat.contact.name.table))
28 | .navigationBarBackButtonHidden(true)
29 | .toolbar {
30 | ToolbarItem(placement: .navigationBarLeading) {
31 | Button(action: { presentationMode.wrappedValue.dismiss() }) {
32 | Image(systemName: "chevron.backward")
33 | }
34 | }
35 | ToolbarItem(placement: .navigationBarTrailing) {
36 | Button(action: {}) {
37 | Image(systemName: "ellipsis")
38 | }
39 | }
40 | }
41 | }
42 | }
43 |
44 | struct ChatDetailView_Previews: PreviewProvider {
45 | static let chat = getDefaultChatList()[0]
46 | static var previews: some View {
47 | ChatDetailView(chat: chat,
48 | viewModel: ChatDetailViewModel(opposite: chat.contact))
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/UI/Chats/ChatsView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChatsView.swift
3 | // SwiftUI-WeChatDemo
4 | //
5 | // Created by Wavky Huang on 2021/07/14.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ChatsView: View {
11 | let chats = getDefaultChatList()
12 | var body: some View {
13 | NavigationView {
14 | ScrollView(showsIndicators: false) {
15 | VStack(spacing:0) {
16 | SearchBarView()
17 | ForEach(chats, id: \.self) { chat in
18 | NavigationLink(destination: createChatDetailView(with: chat)) {
19 | ChatItemView(chat: chat)
20 | }
21 | }
22 | }
23 | }
24 | .navigationBarTitleDisplayMode(.inline)
25 | .navigationTitle(L10n.appName.key)
26 | .toolbar {
27 | ToolbarItem(placement: .navigationBarLeading){
28 | Button(action: {}) {
29 | Image(systemName: "ellipsis")
30 | }
31 | }
32 | ToolbarItem(placement: .navigationBarTrailing){
33 | Button(action: {}) {
34 | Image(systemName: "plus.circle")
35 | }
36 | }
37 | }
38 | }
39 | .accentColor(.black)
40 | }
41 | }
42 |
43 | private func createChatDetailView(with chat: Chat) -> some View {
44 | ChatDetailView(chat: chat,
45 | viewModel: ChatDetailViewModel(
46 | opposite: chat.contact
47 | )
48 | )
49 | }
50 |
51 | struct ChatsView_Previews: PreviewProvider {
52 | static var previews: some View {
53 | ChatsView()
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/discover/discover.moments.imageset/shutter.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/UI/Chats/Detail/ChatFlowView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChatFlowView.swift
3 | // SwiftUI-WeChatDemo
4 | //
5 | // Created by Wavky Huang on 2021/07/14.
6 | //
7 |
8 | import Combine
9 | import SwiftUI
10 |
11 | struct ChatFlowView: View {
12 | @Binding var messageFlow: [ChatMessage]
13 | @StateObject private var keyboard = KeyboardVisibilityReporter()
14 |
15 | var body: some View {
16 | ScrollViewReader { scrollView in
17 | ScrollView(showsIndicators: false) {
18 | VStack(spacing:0) {
19 | ForEach(messageFlow, id: \.self) { message in
20 | ChatMessageView(message: message).onAppear() {
21 | scrollView.scrollTo(message)
22 | }
23 | }
24 | }.onChange(of: keyboard.isVisible) { isVisible in
25 | if isVisible {
26 | scrollView.scrollTo(messageFlow.last!)
27 | }
28 | }
29 | }.background(Asset.Color.chatGray.color)
30 | }
31 | }
32 | }
33 |
34 | struct ChatFlowView_Previews: PreviewProvider {
35 | static let opposite = getDefaultContactList().shuffled()[0]
36 | static let messages = getDefaultChatList().map(\.lastMessage).shuffled()
37 | static let chatMessages = [
38 | ChatMessage(side: .Left(profileImage: opposite.profileImage), text: .LocalizedString(text: messages[0])),
39 | ChatMessage(side: .Right, text: .LocalizedString(text: messages[1])),
40 | ChatMessage(side: .Left(profileImage: opposite.profileImage), text: .LocalizedString(text: messages[2])),
41 | ChatMessage(side: .Right, text: .LocalizedString(text: messages[3]))
42 | ]
43 | static var previews: some View {
44 | ChatFlowView(messageFlow: .constant(chatMessages))
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Data/ContactList.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContactList.swift
3 | // SwiftUI-WeChatDemo
4 | //
5 | // Created by Wavky Huang on 2021/07/14.
6 | //
7 |
8 | import Foundation
9 |
10 | func getDefaultContactList() -> [Contact] {
11 | Friends.allCases.map(\.contact)
12 | }
13 |
14 | struct Contact: Hashable {
15 | let profileImage: String, name: LocalizedString
16 | }
17 |
18 | private enum Friends: CaseIterable {
19 | case Antony, Apple, Bosco, Joe, Microsoft, Muraosa, Ron, Saha, Sony, UncleWang
20 |
21 | var contact: Contact {
22 | switch self {
23 | case .Antony:
24 | return Contact(profileImage: Asset.Image.avatar01.name, name: L10nExtra.Contact.Name.antony)
25 | case .Apple:
26 | return Contact(profileImage: Asset.Image.avatar02.name, name: L10nExtra.Contact.Name.apple)
27 | case .Bosco:
28 | return Contact(profileImage: Asset.Image.avatar03.name, name: L10nExtra.Contact.Name.bosco)
29 | case .Joe:
30 | return Contact(profileImage: Asset.Image.avatar04.name, name: L10nExtra.Contact.Name.joe)
31 | case .Microsoft:
32 | return Contact(profileImage: Asset.Image.avatar05.name, name: L10nExtra.Contact.Name.microsoft)
33 | case .Muraosa:
34 | return Contact(profileImage: Asset.Image.avatar06.name, name: L10nExtra.Contact.Name.muraosa)
35 | case .Ron:
36 | return Contact(profileImage: Asset.Image.avatar07.name, name: L10nExtra.Contact.Name.ron)
37 | case .Saha:
38 | return Contact(profileImage: Asset.Image.avatar08.name, name: L10nExtra.Contact.Name.saha)
39 | case .Sony:
40 | return Contact(profileImage: Asset.Image.avatar09.name, name: L10nExtra.Contact.Name.sony)
41 | case .UncleWang:
42 | return Contact(profileImage: Asset.Image.avatar10.name, name: L10nExtra.Contact.Name.uncleWang)
43 | }
44 | }
45 | }
46 |
47 |
48 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Data/ChatList.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChatList.swift
3 | // SwiftUI-WeChatDemo
4 | //
5 | // Created by Wavky Huang on 2021/07/14.
6 | //
7 |
8 | import Foundation
9 |
10 | func getDefaultChatList() -> [Chat] {
11 | let contacts = getDefaultContactList().shuffled()
12 | let lastMessages = [
13 | L10nExtra.LastMessage.Message.hi,
14 | L10nExtra.LastMessage.Message.hey,
15 | L10nExtra.LastMessage.Message.hello,
16 | L10nExtra.LastMessage.Message.niceToMeetYou,
17 | L10nExtra.LastMessage.Message.howAreYou,
18 | L10nExtra.LastMessage.Message.aloha,
19 | L10nExtra.LastMessage.Message.areYouStillThere,
20 | L10nExtra.LastMessage.Message.iLoveYou,
21 | L10nExtra.LastMessage.Message.theMatrixIsLookingForYou,
22 | L10nExtra.LastMessage.Message.iAmRobot
23 | ].shuffled()
24 | let lastContactTimes: [LastContactTime] = [
25 | .String(time: "13:53"),
26 | .String(time: "11:11"),
27 | .String(time: "03:23"),
28 | .LocalizedString(time: L10nExtra.LastMessage.DateTime.yesterday),
29 | .LocalizedString(time: L10nExtra.LastMessage.DateTime.monday),
30 | .LocalizedString(time: L10nExtra.LastMessage.DateTime.sunday),
31 | .String(time: "2021/04/01"),
32 | .String(time: "2021/01/01"),
33 | .String(time: "2020/11/11"),
34 | .String(time: "2020/02/14")
35 | ]
36 | var chatList: [Chat] = []
37 | for index in 0..<10 {
38 | chatList.append(Chat(contact: contacts[index],
39 | lastMessage: lastMessages[index],
40 | lastContactTime: lastContactTimes[index]))
41 | }
42 | return chatList
43 | }
44 |
45 | struct Chat: Hashable {
46 | let contact: Contact
47 | var lastMessage: LocalizedString, lastContactTime: LastContactTime
48 | }
49 |
50 | enum LastContactTime: Hashable {
51 | case String(time: String),
52 | LocalizedString(time: LocalizedString)
53 | }
54 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/UI/Me/MeView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MeView.swift
3 | // SwiftUI-WeChatDemo
4 | //
5 | // Created by Wavky Huang on 2021/07/14.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct MeView: View {
11 | private let items = getMeItems()
12 | private let spaceHeight: CGFloat = 12
13 |
14 | var body: some View {
15 | NavigationView {
16 | ScrollView(showsIndicators: false) {
17 | VStack(spacing:0) {
18 | NavigationLink(destination: QrCodeView()) {
19 | MyProfileView()
20 | }
21 | Group {
22 | Spacer(minLength: spaceHeight)
23 | LanguageSwitcher()
24 | Spacer(minLength: spaceHeight)
25 | }
26 | Group {
27 | items[.Pay]?.toItemBarView()
28 | Spacer(minLength: spaceHeight)
29 | }
30 | Group {
31 | items[.Favorites]?.toItemBarView(withDivider: true)
32 | items[.Moments]?.toItemBarView(withDivider: true)
33 | items[.Channels]?.toItemBarView(withDivider: true)
34 | items[.Cards]?.toItemBarView(withDivider: true)
35 | items[.Sticker]?.toItemBarView()
36 | Spacer(minLength: spaceHeight)
37 | }
38 | Group {
39 | items[.Settings]?.toItemBarView()
40 | }
41 | Spacer()
42 | }
43 | }
44 | .background(Asset.Color.backgroudGray.color)
45 | .navigationBarTitleDisplayMode(.inline)
46 | .navigationBarHidden(true)
47 | }
48 | .accentColor(.black)
49 | }
50 | }
51 |
52 | struct MeView_Previews: PreviewProvider {
53 | static var previews: some View {
54 | MeView()
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | CFBundleDisplayName
22 | $(PRODUCT_NAME)
23 | LSRequiresIPhoneOS
24 |
25 | UIApplicationSceneManifest
26 |
27 | UIApplicationSupportsMultipleScenes
28 |
29 |
30 | UIApplicationSupportsIndirectInputEvents
31 |
32 | UILaunchScreen
33 |
34 | UIImageName
35 | Splash
36 |
37 | UIRequiredDeviceCapabilities
38 |
39 | armv7
40 |
41 | UISupportedInterfaceOrientations
42 |
43 | UIInterfaceOrientationPortrait
44 | UIInterfaceOrientationLandscapeLeft
45 | UIInterfaceOrientationLandscapeRight
46 |
47 | UISupportedInterfaceOrientations~ipad
48 |
49 | UIInterfaceOrientationPortrait
50 | UIInterfaceOrientationPortraitUpsideDown
51 | UIInterfaceOrientationLandscapeLeft
52 | UIInterfaceOrientationLandscapeRight
53 |
54 | LSApplicationCategoryType
55 | public.app-category.social-networking
56 |
57 |
58 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/UI/Discover/DiscoverView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DiscoverView.swift
3 | // SwiftUI-WeChatDemo
4 | //
5 | // Created by Wavky Huang on 2021/07/14.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct DiscoverView: View {
11 | private let items = getDiscoverItems()
12 | private let spaceHeight: CGFloat = 12
13 |
14 | var body: some View {
15 | NavigationView {
16 | ScrollView(showsIndicators: false) {
17 | VStack(spacing:0) {
18 | Group {
19 | items[.Moments]?.toItemBarView()
20 | Spacer(minLength: spaceHeight)
21 | }
22 | Group {
23 | items[.Channels]?.toItemBarView(withDivider: true)
24 | items[.Live]?.toItemBarView()
25 | Spacer(minLength: spaceHeight)
26 | }
27 | Group {
28 | items[.Scan]?.toItemBarView(withDivider: true)
29 | items[.Shake]?.toItemBarView()
30 | Spacer(minLength: spaceHeight)
31 | }
32 | Group {
33 | items[.TopStories]?.toItemBarView(withDivider: true)
34 | items[.Search]?.toItemBarView()
35 | Spacer(minLength: spaceHeight)
36 | }
37 | Group {
38 | items[.Nearby]?.toItemBarView()
39 | Spacer(minLength: spaceHeight)
40 | }
41 | Group {
42 | items[.MiniPrograms]?.toItemBarView()
43 | }
44 | Spacer()
45 | }
46 | }
47 | .background(Asset.Color.backgroudGray.color)
48 | .navigationBarTitleDisplayMode(.inline)
49 | .navigationTitle(L10n.Tabs.discover.key)
50 | }
51 | }
52 | }
53 |
54 | struct DiscoverView_Previews: PreviewProvider {
55 | static var previews: some View {
56 | DiscoverView()
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/UI/Chats/Detail/ViewModel/ChatDetailViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChatDetailViewModel.swift
3 | // SwiftUI-WeChatDemo
4 | //
5 | // Created by Wavky Huang on 2021/07/15.
6 | //
7 |
8 | import Foundation
9 | import Combine
10 |
11 | class ChatDetailViewModel : ObservableObject {
12 | private let echo = PassthroughSubject()
13 | private var subscription: AnyCancellable? = nil
14 |
15 | @Published var messageFlow: [ChatMessage]
16 | let opposite: Contact
17 |
18 | init(messageFlow: [ChatMessage] = [], opposite: Contact) {
19 | self.messageFlow = messageFlow
20 | self.opposite = opposite
21 | }
22 |
23 | func subscribe() {
24 | subscription = echo.debounce(for: .seconds(1), scheduler: DispatchQueue.main)
25 | .sink(receiveValue: { [weak self] message in
26 | self?.messageFlow.append(message)
27 | })
28 | }
29 |
30 | func sendLastMessage(_ message: LocalizedString) {
31 | messageFlow.append(
32 | ChatMessage(side: .Left(profileImage: opposite.profileImage),
33 | text: .LocalizedString(text: message)))
34 | }
35 |
36 | func send(_ message: String) {
37 | messageFlow.append(ChatMessage(side: .Right, text: .String(text: message)))
38 | }
39 |
40 | func echo(_ message: String) {
41 | echo.send(
42 | ChatMessage(side: .Left(profileImage: opposite.profileImage),
43 | text: .String(text: message.stupidSwap())))
44 | }
45 |
46 | deinit {
47 | subscription?.cancel()
48 | }
49 | }
50 |
51 | private extension String {
52 | func swap(_ a: String, _ b: String) -> String {
53 | let swapMark = "__Swapping__"
54 | let replaceA = self.replacingOccurrences(of: a, with: swapMark)
55 | let replaceB = replaceA.replacingOccurrences(of: b, with: a)
56 | return replaceB.replacingOccurrences(of: swapMark, with: b)
57 | }
58 |
59 | func stupidSwap() -> String {
60 | return self.swap("我", "你")
61 | .swap("I ", "You ")
62 | .swap(" you", " me")
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/UI/TabContainer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TabContainer.swift
3 | // SwiftUI-WeChatDemo
4 | //
5 | // Created by Wavky Huang on 2021/07/14.
6 | //
7 |
8 | import Foundation
9 |
10 | import SwiftUI
11 |
12 | struct TabContainer: View {
13 | @State var selectingTab: Tabs = Tabs.Chats
14 |
15 | var body: some View {
16 | TabView(selection: $selectingTab){
17 | ChatsView().tabItem {
18 | Image(systemName: Tabs.Chats.currentStateIconName(selecting: selectingTab))
19 | Text(L10n.Tabs.chats.key)
20 | }.tag(Tabs.Chats)
21 | ContactsView().tabItem {
22 | Image(systemName: Tabs.Contacts.currentStateIconName(selecting: selectingTab))
23 | Text(L10n.Tabs.contacts.key)
24 | }.tag(Tabs.Contacts)
25 | DiscoverView().tabItem {
26 | Image(systemName: Tabs.Discover.currentStateIconName(selecting: selectingTab))
27 | Text(L10n.Tabs.discover.key)
28 | }.tag(Tabs.Discover)
29 | MeView().tabItem {
30 | Image(systemName: Tabs.Me.currentStateIconName(selecting: selectingTab))
31 | Text(L10n.Tabs.me.key)
32 | }.tag(Tabs.Me)
33 | }.edgesIgnoringSafeArea(.all)
34 | }
35 | }
36 |
37 | enum Tabs {
38 | case Chats, Contacts, Discover, Me
39 |
40 | struct IconName: Hashable {
41 | let on: String, off: String
42 | }
43 |
44 | var iconName: IconName {
45 | switch self {
46 | case .Chats:
47 | return IconName(on: "message.fill", off: "message")
48 | case .Contacts:
49 | return IconName(on: "person.2.fill", off: "person.2")
50 | case .Discover:
51 | return IconName(on: "safari.fill", off: "safari")
52 | case .Me:
53 | return IconName(on: "person.fill", off: "person")
54 | }
55 | }
56 |
57 | func currentStateIconName(selecting: Tabs) -> String {
58 | selecting == self ? iconName.on : iconName.off
59 | }
60 | }
61 |
62 | struct TabContainer_Previews: PreviewProvider {
63 | static var previews: some View {
64 | TabContainer()
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/discover/discover.channels.imageset/butterfly_f.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/UI/Me/MyProfileView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MyProfileView.swift
3 | // SwiftUI-WeChatDemo
4 | //
5 | // Created by Wavky Huang on 2021/07/17.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct MyProfileView: View {
11 | private let profile = MyProfile()
12 |
13 | var body: some View {
14 | HStack{
15 | Image(profile.profileImage)
16 | .resizable()
17 | .scaledToFit()
18 | .frame(width: 70, height: 70, alignment: .center)
19 | .cornerRadius(8.0)
20 | .padding(.horizontal, 10)
21 | VStack(alignment: .leading, spacing: 10) {
22 | Text(L10n.MyProfile.name.key)
23 | .font(.title2)
24 | (Text(L10n.MyProfile.weChatID.key)+Text(profile.weChatID))
25 | .font(.body)
26 | .foregroundColor(Color.gray)
27 | .lineLimit(1)
28 | (Text(Image(systemName: "plus"))+Text(L10n.MyProfile.status.key))
29 | .font(.callout)
30 | .fontWeight(.light)
31 | .foregroundColor(Color.gray)
32 | .padding(2)
33 | .padding(.trailing, 8)
34 | .overlay(Capsule()
35 | .stroke(lineWidth: 0.5)
36 | .foregroundColor(Asset.Color.lightGray.color))
37 | }.offset(x: 0, y: 16.0)
38 | Spacer()
39 | Group {
40 | Color.gray.mask(
41 | Image(Asset.Image.myprofileQrcodeIcon.name)
42 | .resizable()
43 | .scaledToFit()
44 | ).frame(width: 20, height: 20)
45 | Image(systemName: "chevron.forward")
46 | .resizable()
47 | .scaledToFit()
48 | .frame(width: 18, height: 18, alignment: .center)
49 | .foregroundColor(Asset.Color.lightGray.color)
50 | }.offset(x: 0, y: 18.0)
51 | }
52 | .padding()
53 | .padding(.top, 20)
54 | .padding(.bottom, 36)
55 | .frame(maxWidth: .infinity)
56 | .background(Color.white)
57 | }
58 | }
59 |
60 | struct MyProfileView_Previews: PreviewProvider {
61 | static var previews: some View {
62 | ScrollView {
63 | MyProfileView()
64 | .overlay(Rectangle().stroke())
65 | }.background(Color.gray)
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.toptal.com/developers/gitignore/api/swift
2 | # Edit at https://www.toptal.com/developers/gitignore?templates=swift
3 |
4 | ### Swift ###
5 | # Xcode
6 | #
7 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
8 |
9 | ## User settings
10 | xcuserdata/
11 |
12 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
13 | *.xcscmblueprint
14 | *.xccheckout
15 |
16 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
17 | build/
18 | DerivedData/
19 | *.moved-aside
20 | *.pbxuser
21 | !default.pbxuser
22 | *.mode1v3
23 | !default.mode1v3
24 | *.mode2v3
25 | !default.mode2v3
26 | *.perspectivev3
27 | !default.perspectivev3
28 |
29 | ## Obj-C/Swift specific
30 | *.hmap
31 |
32 | ## App packaging
33 | *.ipa
34 | *.dSYM.zip
35 | *.dSYM
36 |
37 | ## Playgrounds
38 | timeline.xctimeline
39 | playground.xcworkspace
40 |
41 | # Swift Package Manager
42 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
43 | # Packages/
44 | # Package.pins
45 | # Package.resolved
46 | # *.xcodeproj
47 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
48 | # hence it is not needed unless you have added a package configuration file to your project
49 | # .swiftpm
50 |
51 | .build/
52 |
53 | # CocoaPods
54 | # We recommend against adding the Pods directory to your .gitignore. However
55 | # you should judge for yourself, the pros and cons are mentioned at:
56 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
57 | # Pods/
58 | # Add this line if you want to avoid checking in source code from the Xcode workspace
59 | # *.xcworkspace
60 |
61 | # Carthage
62 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
63 | # Carthage/Checkouts
64 |
65 | Carthage/Build/
66 |
67 | # Accio dependency management
68 | Dependencies/
69 | .accio/
70 |
71 | # fastlane
72 | # It is recommended to not store the screenshots in the git repo.
73 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
74 | # For more information about the recommended setup visit:
75 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
76 |
77 | fastlane/report.xml
78 | fastlane/Preview.html
79 | fastlane/screenshots/**/*.png
80 | fastlane/test_output
81 |
82 | # Code Injection
83 | # After new code Injection tools there's a generated folder /iOSInjectionProject
84 | # https://github.com/johnno1962/injectionforxcode
85 |
86 | iOSInjectionProject/
87 |
88 | # End of https://www.toptal.com/developers/gitignore/api/swift
89 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/discover/discover.miniPrograms.imageset/science-dna.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/UI/Chats/Detail/ChatMessageView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChatMessage.swift
3 | // SwiftUI-WeChatDemo
4 | //
5 | // Created by Wavky Huang on 2021/07/14.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ChatMessageView: View {
11 | let message: ChatMessage
12 |
13 | var body: some View {
14 | HStack(alignment: .top) {
15 | Image(message.side.profileImage)
16 | .resizable()
17 | .scaledToFit()
18 | .frame(width: 40, height: 40, alignment: .center)
19 | .cornerRadius(4.0)
20 | MessageText(message: message)
21 | Spacer()
22 | }
23 | .padding(10)
24 | .environment(\.layoutDirection, message.side.direction)
25 | }
26 | }
27 |
28 | fileprivate extension Text {
29 | func messageStyle(side: Side) -> some View {
30 | self.padding(10)
31 | .background(side.color)
32 | .clipShape(RoundedRectangle(cornerRadius: 4))
33 | .environment(\.layoutDirection, .leftToRight)
34 | }
35 | }
36 |
37 | fileprivate struct MessageText: View {
38 | let message: ChatMessage
39 | var body: some View {
40 | switch message.text {
41 | case .String(let text):
42 | Text(text).messageStyle(side: message.side)
43 | case .LocalizedString(let text):
44 | Text(text.key, tableName: text.table)
45 | .messageStyle(side: message.side)
46 | }
47 | }
48 | }
49 |
50 | extension Side {
51 | var color: Color {
52 | switch self {
53 | case .Left:
54 | return Color.white
55 | case .Right:
56 | return Asset.Color.chatGreen.color
57 | }
58 | }
59 |
60 | var direction: LayoutDirection {
61 | switch self {
62 | case .Left:
63 | return .leftToRight
64 | case .Right:
65 | return .rightToLeft
66 | }
67 | }
68 | }
69 |
70 | struct ChatMessageView_Previews: PreviewProvider {
71 | static let contact: Contact = getDefaultContactList().shuffled()[0]
72 |
73 | static var previews: some View {
74 | ScrollView() {
75 | VStack(spacing: 0) {
76 | ChatMessageView(message: ChatMessage(side: .Left(profileImage: contact.profileImage), text: .String(text: "Hello?")))
77 | ChatMessageView(message: ChatMessage(side: .Right, text: .String(text: "Hello?")))
78 | ChatMessageView(message: ChatMessage(side: .Left(profileImage: contact.profileImage), text: .String(text: "Hello?Hello?Hello?Hello?Hello?Hello?Hello?Hello?Hello?Hello?Hello?Hello?Hello?Hello?")))
79 | ChatMessageView(message: ChatMessage(side: .Right, text: .String(text: "Hello?Hello?Hello?Hello?Hello?Hello?Hello?Hello?Hello?Hello?Hello?Hello?")))
80 | }
81 | }
82 | .background(Asset.Color.lightGray.color)
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/UI/Me/Data/MeItems.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MeItems.swift
3 | // SwiftUI-WeChatDemo
4 | //
5 | // Created by Wavky Huang on 2021/07/17.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | func getMeItems() -> [MeItemName : ItemBarInfo] {
12 | return [
13 | .Pay: ItemBarInfo(
14 | icon: Asset.Image.mePay.name,
15 | iconPattern: IconPattern.Color(Asset.Color.payGreen.color),
16 | title: L10n.Me.pay.key,
17 | name: nil,
18 | profileImage: nil),
19 | .Favorites: ItemBarInfo(
20 | icon: Asset.Image.meFavorites.name,
21 | iconPattern: IconPattern.AngularGradient(
22 | AngularGradient(gradient: Gradient(
23 | colors: [
24 | Asset.Color.favoritesRed.color,
25 | Asset.Color.favoritesYellow.color,
26 | Asset.Color.favoritesYellow.color,
27 | Asset.Color.favoritesBlue.color,
28 | Asset.Color.favoritesRed.color
29 | ]),
30 | center: .center,
31 | angle: Angle(degrees: 10))),
32 | title: L10n.Me.favorites.key,
33 | name: nil,
34 | profileImage: nil),
35 | .Moments: ItemBarInfo(
36 | icon: Asset.Image.meMoments.name,
37 | iconPattern: IconPattern.Color(Asset.Color.meMomentsBlue.color),
38 | title: L10n.Me.moments.key,
39 | name: nil,
40 | profileImage: nil),
41 | .Channels: ItemBarInfo(
42 | icon: Asset.Image.meChannels.name,
43 | iconPattern: IconPattern.Color(Asset.Color.channelsOrange.color),
44 | title: L10n.Me.channels.key,
45 | name: nil,
46 | profileImage: nil),
47 | .Cards: ItemBarInfo(
48 | icon: Asset.Image.meCards.name,
49 | iconPattern: IconPattern.Color(Asset.Color.cardsBlue.color),
50 | title: L10n.Me.cards.key,
51 | name: nil,
52 | profileImage: nil),
53 | .Sticker: ItemBarInfo(
54 | icon: Asset.Image.meSticker.name,
55 | iconPattern: IconPattern.Color(Asset.Color.stickerYellow.color),
56 | title: L10n.Me.sticker.key,
57 | name: nil,
58 | profileImage: nil),
59 | .Settings: ItemBarInfo(
60 | icon: Asset.Image.meSettings.name,
61 | iconPattern: IconPattern.Color(Asset.Color.settingsBlue.color),
62 | title: L10n.Me.settings.key,
63 | name: nil,
64 | profileImage: nil)
65 | ]
66 | }
67 |
68 | enum MeItemName {
69 | case Pay, Favorites, Moments, Channels,
70 | Cards, Sticker, Settings
71 | }
72 |
73 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/Me/me.settings.imageset/Gear-2.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/UI/Chats/ChatItemView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChatItemView.swift
3 | // SwiftUI-WeChatDemo
4 | //
5 | // Created by Wavky Huang on 2021/07/14.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ChatItemView: View {
11 | @State var chat: Chat
12 | private let isShowBadge: Bool = Float.random(in: 0...1) > 0.45
13 |
14 | var body: some View {
15 | HStack() {
16 | Image(chat.contact.profileImage)
17 | .resizable()
18 | .scaledToFit()
19 | .frame(width: 50, height: 50, alignment: .center)
20 | .cornerRadius(4.0)
21 | .withBadge(isShowBadge)
22 | Spacer(minLength: 15)
23 | VStack(alignment: .leading) {
24 | Spacer()
25 | HStack(alignment: .top) {
26 | VStack(alignment: .leading, spacing: 8) {
27 | Text(chat.contact.name.key,
28 | tableName: chat.contact.name.table)
29 | .font(.title3)
30 | .lineLimit(1)
31 | .frame(alignment: .leading)
32 | Text(chat.lastMessage.key,
33 | tableName: chat.lastMessage.table)
34 | .font(.callout)
35 | .fontWeight(.light)
36 | .lineLimit(1)
37 | .foregroundColor(.gray)
38 | .frame(alignment: .leading)
39 | }
40 | Spacer()
41 | LastContactTimeText(time: chat.lastContactTime)
42 | }
43 | Spacer()
44 | Divider()
45 | }
46 | }
47 | .padding(.horizontal)
48 | .frame(maxWidth: .infinity, minHeight: 80, maxHeight: 80)
49 | }
50 | }
51 |
52 | fileprivate struct LastContactTimeText: View {
53 | let time: LastContactTime
54 | var body: some View {
55 | switch time {
56 | case .String(let time):
57 | return AnyView(Text(time)
58 | .lastContactTimeStyle())
59 | case .LocalizedString(let time):
60 | return AnyView(Text(time.key, tableName: time.table)
61 | .lastContactTimeStyle())
62 | }
63 | }
64 | }
65 |
66 | fileprivate extension Text {
67 | func lastContactTimeStyle() -> some View {
68 | self.font(.caption)
69 | .lineLimit(1)
70 | .foregroundColor(Asset.Color.lightGray.color)
71 | }
72 | }
73 |
74 | struct ChatItemView_Previews: PreviewProvider {
75 | static let chats = getDefaultChatList()
76 | static var previews: some View {
77 | ScrollView {
78 | ChatItemView(chat: chats[0])
79 | ChatItemView(chat: chats[1])
80 | ChatItemView(chat: chats[4])
81 | ChatItemView(chat: chats[6])
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "icon-20@2x.png",
5 | "idiom" : "iphone",
6 | "scale" : "2x",
7 | "size" : "20x20"
8 | },
9 | {
10 | "filename" : "icon-20@3x.png",
11 | "idiom" : "iphone",
12 | "scale" : "3x",
13 | "size" : "20x20"
14 | },
15 | {
16 | "filename" : "icon-29.png",
17 | "idiom" : "iphone",
18 | "scale" : "1x",
19 | "size" : "29x29"
20 | },
21 | {
22 | "filename" : "icon-29@2x.png",
23 | "idiom" : "iphone",
24 | "scale" : "2x",
25 | "size" : "29x29"
26 | },
27 | {
28 | "filename" : "icon-29@3x.png",
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "29x29"
32 | },
33 | {
34 | "filename" : "icon-40@2x.png",
35 | "idiom" : "iphone",
36 | "scale" : "2x",
37 | "size" : "40x40"
38 | },
39 | {
40 | "filename" : "icon-40@3x.png",
41 | "idiom" : "iphone",
42 | "scale" : "3x",
43 | "size" : "40x40"
44 | },
45 | {
46 | "filename" : "icon-60@2x.png",
47 | "idiom" : "iphone",
48 | "scale" : "2x",
49 | "size" : "60x60"
50 | },
51 | {
52 | "filename" : "icon-60@3x.png",
53 | "idiom" : "iphone",
54 | "scale" : "3x",
55 | "size" : "60x60"
56 | },
57 | {
58 | "filename" : "icon-20-ipad.png",
59 | "idiom" : "ipad",
60 | "scale" : "1x",
61 | "size" : "20x20"
62 | },
63 | {
64 | "filename" : "icon-20@2x-ipad.png",
65 | "idiom" : "ipad",
66 | "scale" : "2x",
67 | "size" : "20x20"
68 | },
69 | {
70 | "filename" : "icon-29-ipad.png",
71 | "idiom" : "ipad",
72 | "scale" : "1x",
73 | "size" : "29x29"
74 | },
75 | {
76 | "filename" : "icon-29@2x-ipad.png",
77 | "idiom" : "ipad",
78 | "scale" : "2x",
79 | "size" : "29x29"
80 | },
81 | {
82 | "filename" : "icon-40.png",
83 | "idiom" : "ipad",
84 | "scale" : "1x",
85 | "size" : "40x40"
86 | },
87 | {
88 | "filename" : "icon-40@2x.png",
89 | "idiom" : "ipad",
90 | "scale" : "2x",
91 | "size" : "40x40"
92 | },
93 | {
94 | "filename" : "icon-76.png",
95 | "idiom" : "ipad",
96 | "scale" : "1x",
97 | "size" : "76x76"
98 | },
99 | {
100 | "filename" : "icon-76@2x.png",
101 | "idiom" : "ipad",
102 | "scale" : "2x",
103 | "size" : "76x76"
104 | },
105 | {
106 | "filename" : "icon-83.5@2x.png",
107 | "idiom" : "ipad",
108 | "scale" : "2x",
109 | "size" : "83.5x83.5"
110 | },
111 | {
112 | "filename" : "icon-1024.png",
113 | "idiom" : "ios-marketing",
114 | "scale" : "1x",
115 | "size" : "1024x1024"
116 | }
117 | ],
118 | "info" : {
119 | "author" : "xcode",
120 | "version" : 1
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Resources/Image.xcassets/Me/myprofile.qrcode.icon.imageset/qr-code.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo.xcodeproj/xcshareddata/xcschemes/SwiftUI-WeChatDemo.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
45 |
51 |
52 |
53 |
54 |
60 |
62 |
68 |
69 |
70 |
71 |
73 |
74 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/UI/Discover/Data/DiscoverItems.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DiscoverItems.swift
3 | // SwiftUI-WeChatDemo
4 | //
5 | // Created by Wavky Huang on 2021/07/17.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | func getDiscoverItems() -> [DiscoverItemName : ItemBarInfo] {
12 | let contacts = getDefaultContactList().shuffled()
13 | return [
14 | .Moments: ItemBarInfo(
15 | icon: Asset.Image.discoverMoments.name,
16 | iconPattern: IconPattern.AngularGradient(
17 | AngularGradient(gradient: Gradient(
18 | colors: [
19 | Asset.Color.momentsBlue.color,
20 | Asset.Color.momentsGreen.color,
21 | Asset.Color.momentsYellow.color,
22 | Asset.Color.momentsRed.color,
23 | Asset.Color.momentsBlue.color
24 | ]),
25 | center: .center)),
26 | title: L10n.Discover.moments.key,
27 | name: nil,
28 | profileImage: contacts[0].profileImage),
29 | .Channels: ItemBarInfo(
30 | icon: Asset.Image.discoverChannels.name,
31 | iconPattern: IconPattern.Color(Asset.Color.channelsOrange.color),
32 | title: L10n.Discover.channels.key,
33 | name: contacts[1].name,
34 | profileImage: contacts[1].profileImage),
35 | .Live: ItemBarInfo(
36 | icon: Asset.Image.discoverLive.name,
37 | iconPattern: IconPattern.Color(Asset.Color.liveRed.color),
38 | title: L10n.Discover.live.key,
39 | name: L10n.Discover.Live.onAir,
40 | profileImage: contacts[2].profileImage),
41 | .Scan: ItemBarInfo(
42 | icon: Asset.Image.discoverScan.name,
43 | iconPattern: IconPattern.Color(Asset.Color.scanBlue.color),
44 | title: L10n.Discover.scan.key,
45 | name: nil,
46 | profileImage: nil),
47 | .Shake: ItemBarInfo(
48 | icon: Asset.Image.discoverShake.name,
49 | iconPattern: IconPattern.Color(Asset.Color.shakeBlue.color),
50 | title: L10n.Discover.shake.key,
51 | name: nil,
52 | profileImage: nil),
53 | .TopStories: ItemBarInfo(
54 | icon: Asset.Image.discoverTopStories.name,
55 | iconPattern: IconPattern.Color(Asset.Color.topStoriesYellow.color),
56 | title: L10n.Discover.topStories.key,
57 | name: nil,
58 | profileImage: nil),
59 | .Search: ItemBarInfo(
60 | icon: Asset.Image.discoverSearch.name,
61 | iconPattern: IconPattern.Color(Asset.Color.searchRed.color),
62 | title: L10n.Discover.search.key,
63 | name: nil,
64 | profileImage: nil),
65 | .Nearby: ItemBarInfo(
66 | icon: Asset.Image.discoverNearby.name,
67 | iconPattern: IconPattern.Color(Asset.Color.nearbyBlue.color),
68 | title: L10n.Discover.nearby.key,
69 | name: nil,
70 | profileImage: nil),
71 | .MiniPrograms: ItemBarInfo(
72 | icon: Asset.Image.discoverMiniPrograms.name,
73 | iconPattern: IconPattern.Color(Asset.Color.miniProgramsPurple.color),
74 | title: L10n.Discover.miniPrograms.key,
75 | name: nil,
76 | profileImage: nil)
77 | ]
78 | }
79 |
80 | enum DiscoverItemName {
81 | case Moments, Channels, Live, Scan,
82 | Shake, TopStories, Search, Nearby,
83 | MiniPrograms
84 | }
85 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Generated/stringsExtra.swift:
--------------------------------------------------------------------------------
1 | // Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
2 | // Derived from https://github.com/SwiftGen/SwiftGen/issues/685#issuecomment-782893242
3 | // Origin see https://github.com/SwiftGen/SwiftGen/blob/stable/templates/strings/structured-swift5.stencil
4 | //
5 | // Example:
6 | //
7 | // var body: some View {
8 | // VStack {
9 | // Text(L10n.Login.PageTitle.header.key) // return LocalizedStringKey
10 | // CustomView(string: L10n.Login.Step1.title.text) // return String
11 | // }
12 | // }
13 |
14 | import SwiftUI
15 |
16 | // MARK: - Strings
17 |
18 | public enum L10nExtra {
19 | public enum Contact {
20 | public enum Name {
21 | /// Antony
22 | public static let antony = LocalizedString(table: "Contact", lookupKey: "Name.Antony")
23 | /// Apple
24 | public static let apple = LocalizedString(table: "Contact", lookupKey: "Name.Apple")
25 | /// Bosco
26 | public static let bosco = LocalizedString(table: "Contact", lookupKey: "Name.Bosco")
27 | /// Joe
28 | public static let joe = LocalizedString(table: "Contact", lookupKey: "Name.Joe")
29 | /// Microsoft
30 | public static let microsoft = LocalizedString(table: "Contact", lookupKey: "Name.Microsoft")
31 | /// Muraosa
32 | public static let muraosa = LocalizedString(table: "Contact", lookupKey: "Name.Muraosa")
33 | /// Ron
34 | public static let ron = LocalizedString(table: "Contact", lookupKey: "Name.Ron")
35 | /// Saha
36 | public static let saha = LocalizedString(table: "Contact", lookupKey: "Name.Saha")
37 | /// Sony
38 | public static let sony = LocalizedString(table: "Contact", lookupKey: "Name.Sony")
39 | /// UncleWang
40 | public static let uncleWang = LocalizedString(table: "Contact", lookupKey: "Name.UncleWang")
41 | }
42 | }
43 | public enum LastMessage {
44 | public enum DateTime {
45 | /// Monday
46 | public static let monday = LocalizedString(table: "LastMessage", lookupKey: "DateTime.Monday")
47 | /// Sunday
48 | public static let sunday = LocalizedString(table: "LastMessage", lookupKey: "DateTime.Sunday")
49 | /// Yesterday
50 | public static let yesterday = LocalizedString(table: "LastMessage", lookupKey: "DateTime.Yesterday")
51 | }
52 | public enum Message {
53 | /// Aloha
54 | public static let aloha = LocalizedString(table: "LastMessage", lookupKey: "Message.Aloha")
55 | /// Are you still there?
56 | public static let areYouStillThere = LocalizedString(table: "LastMessage", lookupKey: "Message.AreYouStillThere")
57 | /// Hello
58 | public static let hello = LocalizedString(table: "LastMessage", lookupKey: "Message.Hello")
59 | /// Hey
60 | public static let hey = LocalizedString(table: "LastMessage", lookupKey: "Message.Hey")
61 | /// Hi
62 | public static let hi = LocalizedString(table: "LastMessage", lookupKey: "Message.Hi")
63 | /// How are you?
64 | public static let howAreYou = LocalizedString(table: "LastMessage", lookupKey: "Message.HowAreYou")
65 | /// I am robot.
66 | public static let iAmRobot = LocalizedString(table: "LastMessage", lookupKey: "Message.IAmRobot")
67 | /// I love you!
68 | public static let iLoveYou = LocalizedString(table: "LastMessage", lookupKey: "Message.ILoveYou")
69 | /// Nice to meet you~
70 | public static let niceToMeetYou = LocalizedString(table: "LastMessage", lookupKey: "Message.NiceToMeetYou")
71 | /// The Matrix is looking for you.
72 | public static let theMatrixIsLookingForYou = LocalizedString(table: "LastMessage", lookupKey: "Message.TheMatrixIsLookingForYou")
73 | }
74 | }
75 | }
76 |
77 | // MARK: - Implementation Details
78 |
79 | private final class BundleToken {
80 | static let bundle: Bundle = {
81 | #if SWIFT_PACKAGE
82 | return Bundle.module
83 | #else
84 | return Bundle(for: BundleToken.self)
85 | #endif
86 | }()
87 | }
88 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/UI/Contacts/ContactsView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContactsView.swift
3 | // SwiftUI-WeChatDemo
4 | //
5 | // Created by Wavky Huang on 2021/07/14.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ContactsView: View {
11 | @Environment(\.locale) var locale
12 |
13 | private var contactGroups: [ContactGroup] {
14 | getDefaultContactList()
15 | .group(withLocale: locale)
16 | .sortedList()
17 | }
18 |
19 | var body: some View {
20 | NavigationView {
21 | VStack(spacing: 0) {
22 | SearchBarView()
23 | List {
24 | ForEach(contactGroups, id: \.self) { group in
25 | // Completions will crash if ForEach nest together
26 | ContactSection(contactGroup: group)
27 | }
28 | }
29 | .listStyle(PlainListStyle())
30 | }
31 | .background(Asset.Color.backgroudGray.color)
32 | .navigationBarTitleDisplayMode(.inline)
33 | .navigationTitle(L10n.Tabs.contacts.key)
34 | .toolbar {
35 | ToolbarItem(placement: .navigationBarTrailing){
36 | Button(action: {}) {
37 | Image(systemName: "person.badge.plus")
38 | }
39 | }
40 | }
41 | }.accentColor(.black)
42 | }
43 | }
44 |
45 | private struct ContactSection: View {
46 | let contactGroup: ContactGroup
47 |
48 | var body: some View {
49 | Section(header: Text(String(contactGroup.groupKey)).fontWeight(.regular)) {
50 | ForEach(contactGroup.groupValue, id: \.self) { contact in
51 | ContactItemView(contact: contact)
52 | }
53 | }
54 | }
55 | }
56 |
57 | private extension Array where Element == Contact {
58 | /**
59 | Random contact array to random dictionary.
60 |
61 | `[Contact] -> [Character : [Contact]]`
62 |
63 | Example:
64 | ```
65 | [Contact(China), Contact(Japan), Contact(America), Contact(Chile)]
66 | ↓
67 | ["C": [Contact(Chile), Contact(China)],
68 | "J": [Contact(Japan)],
69 | "A": [Contact(America)]]
70 | ```
71 | */
72 | func group(withLocale locale: Locale) -> [Character : [Contact]] {
73 | let sorted = sorted {
74 | $0.name.text(withLocale: locale) < $1.name.text(withLocale: locale)
75 | }
76 | var sortedDict: [Character : [Contact]] = [:]
77 | for contact in sorted {
78 | guard let key = contact.name.text(withLocale: locale).first else {
79 | continue
80 | }
81 | var group = sortedDict[key] ?? []
82 | group.append(contact)
83 | sortedDict[key] = group
84 | }
85 | return sortedDict
86 | }
87 | }
88 |
89 | private extension Dictionary where Key == Character, Value == [Contact] {
90 | /**
91 | Random dictionary to sorted array of contact's group.
92 |
93 | `[Character : [Contact]] -> [ContactGroup]`
94 |
95 | Example:
96 | ```
97 | ["C": [Contact(Chile), Contact(China)],
98 | "J": [Contact(Japan)],
99 | "A": [Contact(America)]]
100 | ↓
101 | [ContactGroup("A", [Contact(America)]),
102 | ContactGroup("C", [Contact(Chile), Contact(China)]),
103 | ContactGroup("J", [Contact(Japan)])]
104 | ```
105 | */
106 | func sortedList() -> [ContactGroup] {
107 | var sortedList: [ContactGroup] = []
108 | let sortedKeys = keys.sorted()
109 | for key in sortedKeys {
110 | if let contacts = self[key] {
111 | sortedList.append(
112 | ContactGroup(groupKey: key,
113 | groupValue: contacts)
114 | )
115 | }
116 | }
117 | return sortedList
118 | }
119 | }
120 |
121 | private struct ContactGroup: Hashable {
122 | let groupKey: Character
123 | let groupValue: [Contact]
124 | }
125 |
126 | struct ContactsView_Previews: PreviewProvider {
127 | static var previews: some View {
128 | ContactsView()
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/swiftui-strings-template.stencil:
--------------------------------------------------------------------------------
1 | // Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
2 | // Derived from https://github.com/SwiftGen/SwiftGen/issues/685#issuecomment-782893242
3 | // Origin see https://github.com/SwiftGen/SwiftGen/blob/stable/templates/strings/structured-swift5.stencil
4 | //
5 | // Example:
6 | //
7 | // var body: some View {
8 | // VStack {
9 | // Text(L10n.Login.PageTitle.header.key) // return LocalizedStringKey
10 | // CustomView(string: L10n.Login.Step1.title.text) // return String
11 | // }
12 | // }
13 |
14 | {% if tables.count > 0 %}
15 | import SwiftUI
16 |
17 | // MARK: - Strings
18 |
19 | {% macro parametersBlock types %}{% filter removeNewlines:"leading" %}
20 | {% for type in types %}
21 | {% if type == "String" %}
22 | _ p{{forloop.counter}}: Any
23 | {% else %}
24 | _ p{{forloop.counter}}: {{type}}
25 | {% endif %}
26 | {{ ", " if not forloop.last }}
27 | {% endfor %}
28 | {% endfilter %}{% endmacro %}
29 | {% macro argumentsBlock types %}{% filter removeNewlines:"leading" %}
30 | {% for type in types %}
31 | {% if type == "String" %}
32 | String(describing: p{{forloop.counter}})
33 | {% elif type == "UnsafeRawPointer" %}
34 | Int(bitPattern: p{{forloop.counter}})
35 | {% else %}
36 | p{{forloop.counter}}
37 | {% endif %}
38 | {{ ", " if not forloop.last }}
39 | {% endfor %}
40 | {% endfilter %}{% endmacro %}
41 | {% macro recursiveBlock table item %}
42 | {% for string in item.strings %}
43 | {% if not param.noComments %}
44 | /// {{string.translation}}
45 | {% endif %}
46 | {% if string.types %}
47 | public static func {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}({% call parametersBlock string.types %}) -> String {
48 | return tr("{{table}}", "{{string.key}}", {% call argumentsBlock string.types %})
49 | }
50 | {% else %}
51 | public static let {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = LocalizedString(table: "{{table}}", lookupKey: "{{string.key}}")
52 | {% endif %}
53 | {% endfor %}
54 | {% for child in item.children %}
55 | public enum {{child.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
56 | {% filter indent:2 %}{% call recursiveBlock table child %}{% endfilter %}
57 | }
58 | {% endfor %}
59 | {% endmacro %}
60 | {% set enumName %}{{param.enumName|default:"L10n"}}{% endset %}
61 | public enum {{enumName}} {
62 | {% if tables.count > 1 or param.forceFileNameEnum %}
63 | {% for table in tables %}
64 | public enum {{table.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
65 | {% filter indent:2 %}{% call recursiveBlock table.name table.levels %}{% endfilter %}
66 | }
67 | {% endfor %}
68 | {% else %}
69 | {% call recursiveBlock tables.first.name tables.first.levels %}
70 | {% endif %}
71 | }
72 |
73 | // MARK: - Implementation Details
74 | {% if not param.withoutSupporter %}
75 | fileprivate func tr(_ table: String, _ key: String, _ locale: Locale = Locale.current, _ args: CVarArg...) -> String {
76 | let path = Bundle.main.path(forResource: locale.identifier, ofType: "lproj") ?? ""
77 | let format: String
78 | if let bundle = Bundle(path: path) {
79 | format = NSLocalizedString(key, tableName: table, bundle: bundle, comment: "")
80 | } else {
81 | format = BundleToken.bundle.localizedString(forKey: key, value: nil, table: table)
82 | }
83 | return String(format: format, locale: locale, arguments: args)
84 | }
85 |
86 | public struct LocalizedString: Hashable {
87 | let table: String
88 | fileprivate let lookupKey: String
89 |
90 | init(table: String, lookupKey: String) {
91 | self.table = table
92 | self.lookupKey = lookupKey
93 | }
94 |
95 | var key: LocalizedStringKey {
96 | LocalizedStringKey(lookupKey)
97 | }
98 |
99 | var text: String {
100 | tr(table, lookupKey)
101 | }
102 |
103 | func text(withLocale locale: Locale) -> String {
104 | tr(table, lookupKey, locale)
105 | }
106 | }
107 | {% endif %}
108 | {% if not param.bundle %}
109 |
110 | private final class BundleToken {
111 | static let bundle: Bundle = {
112 | #if SWIFT_PACKAGE
113 | return Bundle.module
114 | #else
115 | return Bundle(for: BundleToken.self)
116 | #endif
117 | }()
118 | }
119 | {% endif %}
120 | {% else %}
121 | // No string found
122 | {% endif %}
123 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Generated/strings.swift:
--------------------------------------------------------------------------------
1 | // Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
2 | // Derived from https://github.com/SwiftGen/SwiftGen/issues/685#issuecomment-782893242
3 | // Origin see https://github.com/SwiftGen/SwiftGen/blob/stable/templates/strings/structured-swift5.stencil
4 | //
5 | // Example:
6 | //
7 | // var body: some View {
8 | // VStack {
9 | // Text(L10n.Login.PageTitle.header.key) // return LocalizedStringKey
10 | // CustomView(string: L10n.Login.Step1.title.text) // return String
11 | // }
12 | // }
13 |
14 | import SwiftUI
15 |
16 | // MARK: - Strings
17 |
18 | public enum L10n {
19 | /// WeChat
20 | public static let appName = LocalizedString(table: "Localizable", lookupKey: "AppName")
21 | public enum Discover {
22 | /// Channels
23 | public static let channels = LocalizedString(table: "Localizable", lookupKey: "Discover.Channels")
24 | /// Live
25 | public static let live = LocalizedString(table: "Localizable", lookupKey: "Discover.Live")
26 | /// Mini Programs
27 | public static let miniPrograms = LocalizedString(table: "Localizable", lookupKey: "Discover.MiniPrograms")
28 | /// Moments
29 | public static let moments = LocalizedString(table: "Localizable", lookupKey: "Discover.Moments")
30 | /// Nearby
31 | public static let nearby = LocalizedString(table: "Localizable", lookupKey: "Discover.Nearby")
32 | /// Scan
33 | public static let scan = LocalizedString(table: "Localizable", lookupKey: "Discover.Scan")
34 | /// Search
35 | public static let search = LocalizedString(table: "Localizable", lookupKey: "Discover.Search")
36 | /// Shake
37 | public static let shake = LocalizedString(table: "Localizable", lookupKey: "Discover.Shake")
38 | /// Top Stories
39 | public static let topStories = LocalizedString(table: "Localizable", lookupKey: "Discover.TopStories")
40 | public enum Live {
41 | /// Live
42 | public static let onAir = LocalizedString(table: "Localizable", lookupKey: "Discover.Live.OnAir")
43 | }
44 | }
45 | public enum Me {
46 | /// Cards & Offers
47 | public static let cards = LocalizedString(table: "Localizable", lookupKey: "Me.Cards")
48 | /// Channels
49 | public static let channels = LocalizedString(table: "Localizable", lookupKey: "Me.Channels")
50 | /// Favorites
51 | public static let favorites = LocalizedString(table: "Localizable", lookupKey: "Me.Favorites")
52 | /// Moments
53 | public static let moments = LocalizedString(table: "Localizable", lookupKey: "Me.Moments")
54 | /// Pay
55 | public static let pay = LocalizedString(table: "Localizable", lookupKey: "Me.Pay")
56 | /// Settings
57 | public static let settings = LocalizedString(table: "Localizable", lookupKey: "Me.Settings")
58 | /// Sticker Gallery
59 | public static let sticker = LocalizedString(table: "Localizable", lookupKey: "Me.Sticker")
60 | }
61 | public enum MyProfile {
62 | /// Wavky Huang
63 | public static let name = LocalizedString(table: "Localizable", lookupKey: "MyProfile.Name")
64 | /// Status
65 | public static let status = LocalizedString(table: "Localizable", lookupKey: "MyProfile.Status")
66 | /// WeChat ID:
67 | public static let weChatID = LocalizedString(table: "Localizable", lookupKey: "MyProfile.WeChatID")
68 | }
69 | public enum QrCode {
70 | /// My QR Code
71 | public static let title = LocalizedString(table: "Localizable", lookupKey: "QrCode.Title")
72 | }
73 | public enum SearchBar {
74 | /// Search
75 | public static let hint = LocalizedString(table: "Localizable", lookupKey: "SearchBar.Hint")
76 | }
77 | public enum Tabs {
78 | /// Chats
79 | public static let chats = LocalizedString(table: "Localizable", lookupKey: "Tabs.Chats")
80 | /// Contacts
81 | public static let contacts = LocalizedString(table: "Localizable", lookupKey: "Tabs.Contacts")
82 | /// Discover
83 | public static let discover = LocalizedString(table: "Localizable", lookupKey: "Tabs.Discover")
84 | /// Me
85 | public static let me = LocalizedString(table: "Localizable", lookupKey: "Tabs.Me")
86 | }
87 | }
88 |
89 | // MARK: - Implementation Details
90 | fileprivate func tr(_ table: String, _ key: String, _ locale: Locale = Locale.current, _ args: CVarArg...) -> String {
91 | let path = Bundle.main.path(forResource: locale.identifier, ofType: "lproj") ?? ""
92 | let format: String
93 | if let bundle = Bundle(path: path) {
94 | format = NSLocalizedString(key, tableName: table, bundle: bundle, comment: "")
95 | } else {
96 | format = BundleToken.bundle.localizedString(forKey: key, value: nil, table: table)
97 | }
98 | return String(format: format, locale: locale, arguments: args)
99 | }
100 |
101 | public struct LocalizedString: Hashable {
102 | let table: String
103 | fileprivate let lookupKey: String
104 |
105 | init(table: String, lookupKey: String) {
106 | self.table = table
107 | self.lookupKey = lookupKey
108 | }
109 |
110 | var key: LocalizedStringKey {
111 | LocalizedStringKey(lookupKey)
112 | }
113 |
114 | var text: String {
115 | tr(table, lookupKey)
116 | }
117 |
118 | func text(withLocale locale: Locale) -> String {
119 | tr(table, lookupKey, locale)
120 | }
121 | }
122 |
123 | private final class BundleToken {
124 | static let bundle: Bundle = {
125 | #if SWIFT_PACKAGE
126 | return Bundle.module
127 | #else
128 | return Bundle(for: BundleToken.self)
129 | #endif
130 | }()
131 | }
132 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/UI/Common/ItemBarView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ItemBarView.swift
3 | // SwiftUI-WeChatDemo
4 | //
5 | // Created by Wavky Huang on 2021/07/16.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ItemBarView: View {
11 | let itemBarInfo: ItemBarInfo
12 | let withDivider: Bool
13 | let withBudge: Bool
14 |
15 | var body: some View {
16 | Button(action: {}) {
17 | HStack {
18 | Icon(image: itemBarInfo.icon, pattern: itemBarInfo.iconPattern)
19 | .resizeAsIcon()
20 | Spacer(minLength: 15)
21 |
22 | VStack(spacing:0) {
23 | Spacer(minLength: 0)
24 | HStack {
25 | Text(itemBarInfo.title)
26 | .fontWeight(.light)
27 | .font(.title3)
28 | .lineLimit(1)
29 | .frame(alignment: .leading)
30 | Spacer()
31 | if let name = itemBarInfo.name {
32 | Text(name.key, tableName: name.table)
33 | .font(.callout)
34 | .fontWeight(.light)
35 | .lineLimit(1)
36 | .foregroundColor(.gray)
37 | .frame(alignment: .leading)
38 | }
39 | if let profileImage = itemBarInfo.profileImage {
40 | Image(profileImage)
41 | .resizable()
42 | .scaledToFit()
43 | .frame(width: 40, height: 40, alignment: .center)
44 | .clipShape(Circle())
45 | .withBadge(withBudge, radius: 5)
46 | }
47 | Image(systemName: "chevron.forward")
48 | .resizable()
49 | .scaledToFit()
50 | .frame(width: 18, height: 18, alignment: .center)
51 | .foregroundColor(Asset.Color.lightGray.color)
52 | }
53 | .padding(.vertical, 12)
54 | Spacer(minLength: 0)
55 | if withDivider {
56 | Divider()
57 | }
58 | }
59 | }
60 | }
61 | .accentColor(.black)
62 | .padding(.horizontal)
63 | .background(Color.white)
64 | .frame(height: 65)
65 | .frame(maxWidth: .infinity)
66 | }
67 | }
68 |
69 | private struct Icon: View {
70 | let image: String
71 | let color = Color.red
72 | let pattern: IconPattern?
73 |
74 | var body: some View {
75 | switch pattern {
76 | case .Color(let color):
77 | return AnyView(color.mask(Image(image).fitSize()))
78 | case .AngularGradient(let gradient):
79 | return AnyView(gradient.mask(Image(image).fitSize()))
80 | case .LinearGradient(let gradient):
81 | return AnyView(gradient.mask(Image(image).fitSize()))
82 | case .RadialGradient(let gradient):
83 | return AnyView(gradient.mask(Image(image).fitSize()))
84 | default:
85 | return AnyView(Image(image).fitSize())
86 | }
87 | }
88 | }
89 |
90 | private extension Image {
91 | func fitSize() -> some View {
92 | return self.resizable()
93 | .scaledToFit()
94 | }
95 | }
96 |
97 | private extension View {
98 | func resizeAsIcon() -> some View {
99 | self.frame(width: 30, height: 30, alignment: .center)
100 | }
101 | }
102 |
103 | struct ItemBarInfo {
104 | let icon: String
105 | let iconPattern: IconPattern?
106 | let title: LocalizedStringKey
107 | let name: LocalizedString?
108 | let profileImage: String?
109 | }
110 |
111 | extension ItemBarInfo {
112 | func toItemBarView(withDivider: Bool = false, withBudge: Bool = true) -> ItemBarView {
113 | ItemBarView(itemBarInfo: self, withDivider: withDivider, withBudge: withBudge)
114 | }
115 | }
116 |
117 | enum IconPattern {
118 | case Color(Color)
119 | case AngularGradient(AngularGradient)
120 | case LinearGradient(LinearGradient)
121 | case RadialGradient(RadialGradient)
122 | }
123 |
124 | struct ItemBarView_Previews: PreviewProvider {
125 | static let contacts = getDefaultContactList().shuffled()
126 | static let itemBarInfos = [
127 | ItemBarInfo(icon: Asset.Image.discoverMoments.name, iconPattern: IconPattern.AngularGradient(AngularGradient(gradient: Gradient(colors: [Asset.Color.momentsBlue.color, Asset.Color.momentsGreen.color, Asset.Color.momentsYellow.color, Asset.Color.momentsRed.color, Asset.Color.momentsBlue.color]), center: .center)), title: "Moments", name: contacts[0].name, profileImage: contacts[0].profileImage),
128 | ItemBarInfo(icon: Asset.Image.discoverChannels.name, iconPattern: IconPattern.Color(Asset.Color.channelsOrange.color), title: "Channels", name: nil, profileImage: contacts[1].profileImage),
129 | ItemBarInfo(icon: Asset.Image.discoverLive.name, iconPattern: IconPattern.Color(Asset.Color.liveRed.color), title: "Live", name: nil, profileImage: contacts[2].profileImage)
130 | ]
131 | static var previews: some View {
132 | VStack(spacing: 0) {
133 | ForEach(0..<3, id: \.self) { index in
134 | ItemBarView(itemBarInfo: itemBarInfos[index], withDivider: true, withBudge: true)
135 | }
136 | Spacer()
137 | }
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/SwiftUI-WeChatDemo/Generated/assets.swift:
--------------------------------------------------------------------------------
1 | // swiftlint:disable all
2 | // Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
3 |
4 | #if os(macOS)
5 | import AppKit
6 | #elseif os(iOS)
7 | import UIKit
8 | #elseif os(tvOS) || os(watchOS)
9 | import UIKit
10 | #endif
11 | import SwiftUI
12 |
13 | // Deprecated typealiases
14 | @available(*, deprecated, renamed: "ColorAsset.Color", message: "This typealias will be removed in SwiftGen 7.0")
15 | internal typealias AssetColorTypeAlias = ColorAsset.Color
16 | @available(*, deprecated, renamed: "ImageAsset.Image", message: "This typealias will be removed in SwiftGen 7.0")
17 | internal typealias AssetImageTypeAlias = ImageAsset.Image
18 |
19 | // swiftlint:disable superfluous_disable_command file_length implicit_return
20 |
21 | // MARK: - Asset Catalogs
22 |
23 | // swiftlint:disable identifier_name line_length nesting type_body_length type_name
24 | internal enum Asset {
25 | internal enum Color {
26 | internal static let accentColor = ColorAsset(name: "AccentColor")
27 | internal static let backgroudGray = ColorAsset(name: "BackgroudGray")
28 | internal static let chatGray = ColorAsset(name: "ChatGray")
29 | internal static let chatGreen = ColorAsset(name: "ChatGreen")
30 | internal static let channelsOrange = ColorAsset(name: "ChannelsOrange")
31 | internal static let liveRed = ColorAsset(name: "LiveRed")
32 | internal static let miniProgramsPurple = ColorAsset(name: "MiniProgramsPurple")
33 | internal static let momentsBlue = ColorAsset(name: "moments.blue")
34 | internal static let momentsGreen = ColorAsset(name: "moments.green")
35 | internal static let momentsRed = ColorAsset(name: "moments.red")
36 | internal static let momentsYellow = ColorAsset(name: "moments.yellow")
37 | internal static let nearbyBlue = ColorAsset(name: "NearbyBlue")
38 | internal static let scanBlue = ColorAsset(name: "ScanBlue")
39 | internal static let searchRed = ColorAsset(name: "SearchRed")
40 | internal static let shakeBlue = ColorAsset(name: "ShakeBlue")
41 | internal static let topStoriesYellow = ColorAsset(name: "TopStoriesYellow")
42 | internal static let lightGray = ColorAsset(name: "LightGray")
43 | internal static let cardsBlue = ColorAsset(name: "CardsBlue")
44 | internal static let favoritesBlue = ColorAsset(name: "favorites.blue")
45 | internal static let favoritesRed = ColorAsset(name: "favorites.red")
46 | internal static let favoritesYellow = ColorAsset(name: "favorites.yellow")
47 | internal static let meMomentsBlue = ColorAsset(name: "MeMomentsBlue")
48 | internal static let payGreen = ColorAsset(name: "PayGreen")
49 | internal static let settingsBlue = ColorAsset(name: "SettingsBlue")
50 | internal static let stickerYellow = ColorAsset(name: "StickerYellow")
51 | internal static let searchBarBackgroundGray = ColorAsset(name: "SearchBarBackgroundGray")
52 | internal static let searchGray = ColorAsset(name: "SearchGray")
53 | internal static let tabbarGray = ColorAsset(name: "TabbarGray")
54 | internal static let topBarGray = ColorAsset(name: "TopBarGray")
55 | }
56 | internal enum Image {
57 | internal static let splash = ImageAsset(name: "Splash")
58 | internal static let avatar01 = ImageAsset(name: "avatar01")
59 | internal static let avatar02 = ImageAsset(name: "avatar02")
60 | internal static let avatar03 = ImageAsset(name: "avatar03")
61 | internal static let avatar04 = ImageAsset(name: "avatar04")
62 | internal static let avatar05 = ImageAsset(name: "avatar05")
63 | internal static let avatar06 = ImageAsset(name: "avatar06")
64 | internal static let avatar07 = ImageAsset(name: "avatar07")
65 | internal static let avatar08 = ImageAsset(name: "avatar08")
66 | internal static let avatar09 = ImageAsset(name: "avatar09")
67 | internal static let avatar10 = ImageAsset(name: "avatar10")
68 | internal static let avatarMe = ImageAsset(name: "avatar_me")
69 | internal static let discoverChannels = ImageAsset(name: "discover.channels")
70 | internal static let discoverLive = ImageAsset(name: "discover.live")
71 | internal static let discoverMiniPrograms = ImageAsset(name: "discover.miniPrograms")
72 | internal static let discoverMoments = ImageAsset(name: "discover.moments")
73 | internal static let discoverNearby = ImageAsset(name: "discover.nearby")
74 | internal static let discoverScan = ImageAsset(name: "discover.scan")
75 | internal static let discoverSearch = ImageAsset(name: "discover.search")
76 | internal static let discoverShake = ImageAsset(name: "discover.shake")
77 | internal static let discoverTopStories = ImageAsset(name: "discover.topStories")
78 | internal static let meCards = ImageAsset(name: "me.cards")
79 | internal static let meChannels = ImageAsset(name: "me.channels")
80 | internal static let meFavorites = ImageAsset(name: "me.favorites")
81 | internal static let meMoments = ImageAsset(name: "me.moments")
82 | internal static let mePay = ImageAsset(name: "me.pay")
83 | internal static let meSettings = ImageAsset(name: "me.settings")
84 | internal static let meSticker = ImageAsset(name: "me.sticker")
85 | internal static let myprofileQrcodeIcon = ImageAsset(name: "myprofile.qrcode.icon")
86 | internal static let myprofileQrcode = ImageAsset(name: "myprofile.qrcode")
87 | }
88 | }
89 | // swiftlint:enable identifier_name line_length nesting type_body_length type_name
90 |
91 | // MARK: - Implementation Details
92 |
93 | internal final class ColorAsset {
94 | internal fileprivate(set) var name: String
95 |
96 | #if os(macOS)
97 | internal typealias Color = NSColor
98 | #elseif os(iOS) || os(tvOS) || os(watchOS)
99 | internal typealias Color = UIColor
100 | #endif
101 | @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
102 | internal private(set) lazy var color: SwiftUI.Color = { SwiftUI.Color(name) }()
103 |
104 | @available(macOS 10.13, *)
105 | @available(iOS, unavailable)
106 | @available(tvOS, unavailable)
107 | @available(watchOS, unavailable)
108 | internal private(set) lazy var nsColor: Color = {
109 | guard let color = Color(asset: self) else {
110 | fatalError("Unable to load color asset named \(name).")
111 | }
112 | return color
113 | }()
114 |
115 | @available(macOS, unavailable)
116 | @available(iOS 11.0, tvOS 11.0, watchOS 4.0, *)
117 | internal private(set) lazy var uiColor: Color = {
118 | guard let color = Color(asset: self) else {
119 | fatalError("Unable to load color asset named \(name).")
120 | }
121 | return color
122 | }()
123 |
124 | fileprivate init(name: String) {
125 | self.name = name
126 | }
127 | }
128 |
129 | internal extension ColorAsset.Color {
130 | @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *)
131 | convenience init?(asset: ColorAsset) {
132 | let bundle = BundleToken.bundle
133 | #if os(iOS) || os(tvOS)
134 | self.init(named: asset.name, in: bundle, compatibleWith: nil)
135 | #elseif os(macOS)
136 | self.init(named: NSColor.Name(asset.name), bundle: bundle)
137 | #elseif os(watchOS)
138 | self.init(named: asset.name)
139 | #endif
140 | }
141 | }
142 |
143 | internal struct ImageAsset {
144 | internal fileprivate(set) var name: String
145 |
146 | #if os(macOS)
147 | internal typealias Image = NSImage
148 | #elseif os(iOS) || os(tvOS) || os(watchOS)
149 | internal typealias Image = UIImage
150 | #endif
151 |
152 | internal var image: Image {
153 | let bundle = BundleToken.bundle
154 | #if os(iOS) || os(tvOS)
155 | let image = Image(named: name, in: bundle, compatibleWith: nil)
156 | #elseif os(macOS)
157 | let name = NSImage.Name(self.name)
158 | let image = (bundle == .main) ? NSImage(named: name) : bundle.image(forResource: name)
159 | #elseif os(watchOS)
160 | let image = Image(named: name)
161 | #endif
162 | guard let result = image else {
163 | fatalError("Unable to load image asset named \(name).")
164 | }
165 | return result
166 | }
167 | }
168 |
169 | internal extension ImageAsset.Image {
170 | @available(macOS, deprecated,
171 | message: "This initializer is unsafe on macOS, please use the ImageAsset.image property")
172 | convenience init?(asset: ImageAsset) {
173 | #if os(iOS) || os(tvOS)
174 | let bundle = BundleToken.bundle
175 | self.init(named: asset.name, in: bundle, compatibleWith: nil)
176 | #elseif os(macOS)
177 | self.init(named: NSImage.Name(asset.name))
178 | #elseif os(watchOS)
179 | self.init(named: asset.name)
180 | #endif
181 | }
182 | }
183 |
184 | // swiftlint:disable convenience_type
185 | private final class BundleToken {
186 | static let bundle: Bundle = {
187 | #if SWIFT_PACKAGE
188 | return Bundle.module
189 | #else
190 | return Bundle(for: BundleToken.self)
191 | #endif
192 | }()
193 | }
194 | // swiftlint:enable convenience_type
195 |
--------------------------------------------------------------------------------
/swiftui-assets-template.stencil:
--------------------------------------------------------------------------------
1 | // swiftlint:disable all
2 | // Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
3 |
4 | {% if catalogs %}
5 | {% set enumName %}{{param.enumName|default:"Asset"}}{% endset %}
6 | {% set arResourceGroupType %}{{param.arResourceGroupTypeName|default:"ARResourceGroupAsset"}}{% endset %}
7 | {% set colorType %}{{param.colorTypeName|default:"ColorAsset"}}{% endset %}
8 | {% set dataType %}{{param.dataTypeName|default:"DataAsset"}}{% endset %}
9 | {% set imageType %}{{param.imageTypeName|default:"ImageAsset"}}{% endset %}
10 | {% set forceNamespaces %}{{param.forceProvidesNamespaces|default:"false"}}{% endset %}
11 | {% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
12 | #if os(macOS)
13 | import AppKit
14 | #elseif os(iOS)
15 | {% if resourceCount.arresourcegroup > 0 %}
16 | import ARKit
17 | {% endif %}
18 | import UIKit
19 | #elseif os(tvOS) || os(watchOS)
20 | import UIKit
21 | #endif
22 | import SwiftUI
23 |
24 | // Deprecated typealiases
25 | {% if resourceCount.color > 0 %}
26 | @available(*, deprecated, renamed: "{{colorType}}.Color", message: "This typealias will be removed in SwiftGen 7.0")
27 | {{accessModifier}} typealias {{param.colorAliasName|default:"AssetColorTypeAlias"}} = {{colorType}}.Color
28 | {% endif %}
29 | {% if resourceCount.image > 0 %}
30 | @available(*, deprecated, renamed: "{{imageType}}.Image", message: "This typealias will be removed in SwiftGen 7.0")
31 | {{accessModifier}} typealias {{param.imageAliasName|default:"AssetImageTypeAlias"}} = {{imageType}}.Image
32 | {% endif %}
33 |
34 | // swiftlint:disable superfluous_disable_command file_length implicit_return
35 |
36 | // MARK: - Asset Catalogs
37 |
38 | {% macro enumBlock assets %}
39 | {% call casesBlock assets %}
40 | {% if param.allValues %}
41 |
42 | // swiftlint:disable trailing_comma
43 | {% if resourceCount.arresourcegroup > 0 %}
44 | {{accessModifier}} static let allResourceGroups: [{{arResourceGroupType}}] = [
45 | {% filter indent:2 %}{% call allValuesBlock assets "arresourcegroup" "" %}{% endfilter %}
46 | ]
47 | {% endif %}
48 | {% if resourceCount.color > 0 %}
49 | {{accessModifier}} static let allColors: [{{colorType}}] = [
50 | {% filter indent:2 %}{% call allValuesBlock assets "color" "" %}{% endfilter %}
51 | ]
52 | {% endif %}
53 | {% if resourceCount.data > 0 %}
54 | {{accessModifier}} static let allDataAssets: [{{dataType}}] = [
55 | {% filter indent:2 %}{% call allValuesBlock assets "data" "" %}{% endfilter %}
56 | ]
57 | {% endif %}
58 | {% if resourceCount.image > 0 %}
59 | {{accessModifier}} static let allImages: [{{imageType}}] = [
60 | {% filter indent:2 %}{% call allValuesBlock assets "image" "" %}{% endfilter %}
61 | ]
62 | {% endif %}
63 | // swiftlint:enable trailing_comma
64 | {% endif %}
65 | {% endmacro %}
66 | {% macro casesBlock assets %}
67 | {% for asset in assets %}
68 | {% if asset.type == "arresourcegroup" %}
69 | {{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{arResourceGroupType}}(name: "{{asset.value}}")
70 | {% elif asset.type == "color" %}
71 | {{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{colorType}}(name: "{{asset.value}}")
72 | {% elif asset.type == "data" %}
73 | {{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{dataType}}(name: "{{asset.value}}")
74 | {% elif asset.type == "image" %}
75 | {{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{imageType}}(name: "{{asset.value}}")
76 | {% elif asset.items and ( forceNamespaces == "true" or asset.isNamespaced == "true" ) %}
77 | {{accessModifier}} enum {{asset.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
78 | {% filter indent:2 %}{% call casesBlock asset.items %}{% endfilter %}
79 | }
80 | {% elif asset.items %}
81 | {% call casesBlock asset.items %}
82 | {% endif %}
83 | {% endfor %}
84 | {% endmacro %}
85 | {% macro allValuesBlock assets filter prefix %}
86 | {% for asset in assets %}
87 | {% if asset.type == filter %}
88 | {{prefix}}{{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}},
89 | {% elif asset.items and ( forceNamespaces == "true" or asset.isNamespaced == "true" ) %}
90 | {% set prefix2 %}{{prefix}}{{asset.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}.{% endset %}
91 | {% call allValuesBlock asset.items filter prefix2 %}
92 | {% elif asset.items %}
93 | {% call allValuesBlock asset.items filter prefix %}
94 | {% endif %}
95 | {% endfor %}
96 | {% endmacro %}
97 | // swiftlint:disable identifier_name line_length nesting type_body_length type_name
98 | {{accessModifier}} enum {{enumName}} {
99 | {% if catalogs.count > 1 or param.forceFileNameEnum %}
100 | {% for catalog in catalogs %}
101 | {{accessModifier}} enum {{catalog.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
102 | {% filter indent:2 %}{% call enumBlock catalog.assets %}{% endfilter %}
103 | }
104 | {% endfor %}
105 | {% else %}
106 | {% call enumBlock catalogs.first.assets %}
107 | {% endif %}
108 | }
109 | // swiftlint:enable identifier_name line_length nesting type_body_length type_name
110 |
111 | // MARK: - Implementation Details
112 | {% if resourceCount.arresourcegroup > 0 %}
113 |
114 | {{accessModifier}} struct {{arResourceGroupType}} {
115 | {{accessModifier}} fileprivate(set) var name: String
116 |
117 | #if os(iOS)
118 | @available(iOS 11.3, *)
119 | {{accessModifier}} var referenceImages: Set {
120 | return ARReferenceImage.referenceImages(in: self)
121 | }
122 |
123 | @available(iOS 12.0, *)
124 | {{accessModifier}} var referenceObjects: Set {
125 | return ARReferenceObject.referenceObjects(in: self)
126 | }
127 | #endif
128 | }
129 |
130 | #if os(iOS)
131 | @available(iOS 11.3, *)
132 | {{accessModifier}} extension ARReferenceImage {
133 | static func referenceImages(in asset: {{arResourceGroupType}}) -> Set {
134 | let bundle = {{param.bundle|default:"BundleToken.bundle"}}
135 | return referenceImages(inGroupNamed: asset.name, bundle: bundle) ?? Set()
136 | }
137 | }
138 |
139 | @available(iOS 12.0, *)
140 | {{accessModifier}} extension ARReferenceObject {
141 | static func referenceObjects(in asset: {{arResourceGroupType}}) -> Set {
142 | let bundle = {{param.bundle|default:"BundleToken.bundle"}}
143 | return referenceObjects(inGroupNamed: asset.name, bundle: bundle) ?? Set()
144 | }
145 | }
146 | #endif
147 | {% endif %}
148 | {% if resourceCount.color > 0 %}
149 |
150 | {{accessModifier}} final class {{colorType}} {
151 | {{accessModifier}} fileprivate(set) var name: String
152 |
153 | #if os(macOS)
154 | {{accessModifier}} typealias Color = NSColor
155 | #elseif os(iOS) || os(tvOS) || os(watchOS)
156 | {{accessModifier}} typealias Color = UIColor
157 | #endif
158 |
159 | @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
160 | internal private(set) lazy var color: SwiftUI.Color = { SwiftUI.Color(name) }()
161 |
162 | @available(macOS 10.13, *)
163 | @available(iOS, unavailable)
164 | @available(tvOS, unavailable)
165 | @available(watchOS, unavailable)
166 | internal private(set) lazy var nsColor: Color = {
167 | guard let color = Color(asset: self) else {
168 | fatalError("Unable to load color asset named \(name).")
169 | }
170 | return color
171 | }()
172 |
173 | @available(macOS, unavailable)
174 | @available(iOS 11.0, tvOS 11.0, watchOS 4.0, *)
175 | internal private(set) lazy var uiColor: Color = {
176 | guard let color = Color(asset: self) else {
177 | fatalError("Unable to load color asset named \(name).")
178 | }
179 | return color
180 | }()
181 |
182 | fileprivate init(name: String) {
183 | self.name = name
184 | }
185 | }
186 |
187 | {{accessModifier}} extension {{colorType}}.Color {
188 | @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *)
189 | convenience init?(asset: {{colorType}}) {
190 | let bundle = {{param.bundle|default:"BundleToken.bundle"}}
191 | #if os(iOS) || os(tvOS)
192 | self.init(named: asset.name, in: bundle, compatibleWith: nil)
193 | #elseif os(macOS)
194 | self.init(named: NSColor.Name(asset.name), bundle: bundle)
195 | #elseif os(watchOS)
196 | self.init(named: asset.name)
197 | #endif
198 | }
199 | }
200 | {% endif %}
201 | {% if resourceCount.data > 0 %}
202 |
203 | {{accessModifier}} struct {{dataType}} {
204 | {{accessModifier}} fileprivate(set) var name: String
205 |
206 | #if os(iOS) || os(tvOS) || os(macOS)
207 | @available(iOS 9.0, macOS 10.11, *)
208 | {{accessModifier}} var data: NSDataAsset {
209 | guard let data = NSDataAsset(asset: self) else {
210 | fatalError("Unable to load data asset named \(name).")
211 | }
212 | return data
213 | }
214 | #endif
215 | }
216 |
217 | #if os(iOS) || os(tvOS) || os(macOS)
218 | @available(iOS 9.0, macOS 10.11, *)
219 | {{accessModifier}} extension NSDataAsset {
220 | convenience init?(asset: {{dataType}}) {
221 | let bundle = {{param.bundle|default:"BundleToken.bundle"}}
222 | #if os(iOS) || os(tvOS)
223 | self.init(name: asset.name, bundle: bundle)
224 | #elseif os(macOS)
225 | self.init(name: NSDataAsset.Name(asset.name), bundle: bundle)
226 | #endif
227 | }
228 | }
229 | #endif
230 | {% endif %}
231 | {% if resourceCount.image > 0 %}
232 |
233 | {{accessModifier}} struct {{imageType}} {
234 | {{accessModifier}} fileprivate(set) var name: String
235 |
236 | #if os(macOS)
237 | {{accessModifier}} typealias Image = NSImage
238 | #elseif os(iOS) || os(tvOS) || os(watchOS)
239 | {{accessModifier}} typealias Image = UIImage
240 | #endif
241 |
242 | {{accessModifier}} var image: Image {
243 | let bundle = {{param.bundle|default:"BundleToken.bundle"}}
244 | #if os(iOS) || os(tvOS)
245 | let image = Image(named: name, in: bundle, compatibleWith: nil)
246 | #elseif os(macOS)
247 | let name = NSImage.Name(self.name)
248 | let image = (bundle == .main) ? NSImage(named: name) : bundle.image(forResource: name)
249 | #elseif os(watchOS)
250 | let image = Image(named: name)
251 | #endif
252 | guard let result = image else {
253 | fatalError("Unable to load image asset named \(name).")
254 | }
255 | return result
256 | }
257 | }
258 |
259 | {{accessModifier}} extension {{imageType}}.Image {
260 | @available(macOS, deprecated,
261 | message: "This initializer is unsafe on macOS, please use the {{imageType}}.image property")
262 | convenience init?(asset: {{imageType}}) {
263 | #if os(iOS) || os(tvOS)
264 | let bundle = {{param.bundle|default:"BundleToken.bundle"}}
265 | self.init(named: asset.name, in: bundle, compatibleWith: nil)
266 | #elseif os(macOS)
267 | self.init(named: NSImage.Name(asset.name))
268 | #elseif os(watchOS)
269 | self.init(named: asset.name)
270 | #endif
271 | }
272 | }
273 | {% endif %}
274 | {% if not param.bundle %}
275 |
276 | // swiftlint:disable convenience_type
277 | private final class BundleToken {
278 | static let bundle: Bundle = {
279 | #if SWIFT_PACKAGE
280 | return Bundle.module
281 | #else
282 | return Bundle(for: BundleToken.self)
283 | #endif
284 | }()
285 | }
286 | // swiftlint:enable convenience_type
287 | {% endif %}
288 | {% else %}
289 | // No assets found
290 | {% endif %}
291 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------