diff --git a/ios-sample/EnvConfig.xcconfig b/ios-sample/EnvConfig.xcconfig new file mode 100644 index 0000000..4d3ddd0 --- /dev/null +++ b/ios-sample/EnvConfig.xcconfig @@ -0,0 +1,13 @@ +// +// EnvConfig.xcconfig +// ios-sample +// +// Created by Koji Osugi on 24/01/23. +// Copyright © 2023 orgName. All rights reserved. +// + +// Configuration settings file format documentation can be found at: +// https://help.apple.com/xcode/#/dev745c5c974 + + +API_KEY = // YOUR API KEY diff --git a/ios-sample/README.md b/ios-sample/README.md new file mode 100644 index 0000000..634f68e --- /dev/null +++ b/ios-sample/README.md @@ -0,0 +1,11 @@ +# YChat GPT iOS Sample + +## Setup + +You need to supply an API key for running this sample successfully. + +### Steps + +1. Go to the `EnvConfig.xcconfig` file located in the root folder of this sample project; + +2. Set your API key in the `API_KEY` variable. Click [here](https://beta.openai.com/docs/api-reference/authentication) to get more information on how to get the api key. diff --git a/ios-sample/ios-sample.xcodeproj/project.pbxproj b/ios-sample/ios-sample.xcodeproj/project.pbxproj index 2a6475d..7ae7c8b 100644 --- a/ios-sample/ios-sample.xcodeproj/project.pbxproj +++ b/ios-sample/ios-sample.xcodeproj/project.pbxproj @@ -10,7 +10,25 @@ 058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557BA273AAA24004C7B11 /* Assets.xcassets */; }; 058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; }; 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iOSApp.swift */; }; - 7555FF83242A565900829871 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* ContentView.swift */; }; + 488448D9297B8419005B8A24 /* CompletionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 488448D8297B8419005B8A24 /* CompletionViewModel.swift */; }; + 488448DD297B8DD2005B8A24 /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = 488448DC297B8DD2005B8A24 /* Color.swift */; }; + 488448DF297B8DF6005B8A24 /* Typography.swift in Sources */ = {isa = PBXBuildFile; fileRef = 488448DE297B8DF6005B8A24 /* Typography.swift */; }; + 488448E3297B8F1B005B8A24 /* DefaultTextFieldStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 488448E2297B8F1B005B8A24 /* DefaultTextFieldStyle.swift */; }; + 488448E6297B9116005B8A24 /* RoundedCorner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 488448E5297B9116005B8A24 /* RoundedCorner.swift */; }; + 488448E8297B9198005B8A24 /* ViewModifer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 488448E7297B9198005B8A24 /* ViewModifer.swift */; }; + 488448EA297B9843005B8A24 /* Icon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 488448E9297B9843005B8A24 /* Icon.swift */; }; + 488448ED297CAC4F005B8A24 /* ChatMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 488448EC297CAC4F005B8A24 /* ChatMessage.swift */; }; + 488448EF297CD1E8005B8A24 /* ToolbarModifer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 488448EE297CD1E8005B8A24 /* ToolbarModifer.swift */; }; + 488448F1297CD209005B8A24 /* ImageButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 488448F0297CD209005B8A24 /* ImageButton.swift */; }; + 488448F6297CDF67005B8A24 /* SideMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 488448F5297CDF67005B8A24 /* SideMenu.swift */; }; + 488448FC297CE004005B8A24 /* SplashView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 488448FB297CE004005B8A24 /* SplashView.swift */; }; + 488448FF297CE035005B8A24 /* AppRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 488448FE297CE035005B8A24 /* AppRouter.swift */; }; + 48844903297CE07C005B8A24 /* NavGraph.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48844902297CE07C005B8A24 /* NavGraph.swift */; }; + 48844905297CE094005B8A24 /* TransitionAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48844904297CE094005B8A24 /* TransitionAnimation.swift */; }; + 4884490A297CE1C5005B8A24 /* MainRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48844909297CE1C5005B8A24 /* MainRouter.swift */; }; + 4884490D297CE1E6005B8A24 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4884490C297CE1E6005B8A24 /* MainView.swift */; }; + 489A546D297F93F700A6532C /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 489A546C297F93F700A6532C /* Config.swift */; }; + 7555FF83242A565900829871 /* CompletionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* CompletionView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -30,8 +48,28 @@ 058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = ""; }; + 488448D8297B8419005B8A24 /* CompletionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletionViewModel.swift; sourceTree = ""; }; + 488448DC297B8DD2005B8A24 /* Color.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Color.swift; sourceTree = ""; }; + 488448DE297B8DF6005B8A24 /* Typography.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Typography.swift; sourceTree = ""; }; + 488448E2297B8F1B005B8A24 /* DefaultTextFieldStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultTextFieldStyle.swift; sourceTree = ""; }; + 488448E5297B9116005B8A24 /* RoundedCorner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedCorner.swift; sourceTree = ""; }; + 488448E7297B9198005B8A24 /* ViewModifer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModifer.swift; sourceTree = ""; }; + 488448E9297B9843005B8A24 /* Icon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Icon.swift; sourceTree = ""; }; + 488448EC297CAC4F005B8A24 /* ChatMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessage.swift; sourceTree = ""; }; + 488448EE297CD1E8005B8A24 /* ToolbarModifer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarModifer.swift; sourceTree = ""; }; + 488448F0297CD209005B8A24 /* ImageButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageButton.swift; sourceTree = ""; }; + 488448F5297CDF67005B8A24 /* SideMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideMenu.swift; sourceTree = ""; }; + 488448FB297CE004005B8A24 /* SplashView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashView.swift; sourceTree = ""; }; + 488448FE297CE035005B8A24 /* AppRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRouter.swift; sourceTree = ""; }; + 48844902297CE07C005B8A24 /* NavGraph.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavGraph.swift; sourceTree = ""; }; + 48844904297CE094005B8A24 /* TransitionAnimation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransitionAnimation.swift; sourceTree = ""; }; + 48844909297CE1C5005B8A24 /* MainRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainRouter.swift; sourceTree = ""; }; + 4884490C297CE1E6005B8A24 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = ""; }; + 489A546A297F916C00A6532C /* EnvConfig.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = EnvConfig.xcconfig; sourceTree = ""; }; + 489A546C297F93F700A6532C /* Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; }; + 489A5471297F99FC00A6532C /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 7555FF7B242A565900829871 /* ios-sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "ios-sample.app"; sourceTree = BUILT_PRODUCTS_DIR; }; - 7555FF82242A565900829871 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 7555FF82242A565900829871 /* CompletionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletionView.swift; sourceTree = ""; }; 7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ @@ -54,12 +92,182 @@ path = "Preview Content"; sourceTree = ""; }; + 488448DA297B8D19005B8A24 /* App */ = { + isa = PBXGroup; + children = ( + 488448FA297CDFF2005B8A24 /* Presenter */, + 488448FD297CE019005B8A24 /* Router */, + 2152FB032600AC8F00CF470E /* iOSApp.swift */, + ); + path = App; + sourceTree = ""; + }; + 488448DB297B8D57005B8A24 /* UI */ = { + isa = PBXGroup; + children = ( + 488448E4297B9108005B8A24 /* Modifier */, + 488448E1297B8EF9005B8A24 /* Component */, + 488448E0297B8EF2005B8A24 /* Theme */, + ); + path = UI; + sourceTree = ""; + }; + 488448E0297B8EF2005B8A24 /* Theme */ = { + isa = PBXGroup; + children = ( + 488448DC297B8DD2005B8A24 /* Color.swift */, + 488448DE297B8DF6005B8A24 /* Typography.swift */, + 488448E9297B9843005B8A24 /* Icon.swift */, + ); + path = Theme; + sourceTree = ""; + }; + 488448E1297B8EF9005B8A24 /* Component */ = { + isa = PBXGroup; + children = ( + 488448F4297CDF54005B8A24 /* Bar */, + 488448F3297CDF4B005B8A24 /* TextField */, + 488448F2297CDF3F005B8A24 /* Button */, + ); + path = Component; + sourceTree = ""; + }; + 488448E4297B9108005B8A24 /* Modifier */ = { + isa = PBXGroup; + children = ( + 488448E5297B9116005B8A24 /* RoundedCorner.swift */, + 488448E7297B9198005B8A24 /* ViewModifer.swift */, + 488448EE297CD1E8005B8A24 /* ToolbarModifer.swift */, + ); + path = Modifier; + sourceTree = ""; + }; + 488448EB297CAC28005B8A24 /* Completion */ = { + isa = PBXGroup; + children = ( + 488448F9297CDFD0005B8A24 /* ViewModel */, + 488448F8297CDFC6005B8A24 /* Model */, + 7555FF82242A565900829871 /* CompletionView.swift */, + ); + path = Completion; + sourceTree = ""; + }; + 488448F2297CDF3F005B8A24 /* Button */ = { + isa = PBXGroup; + children = ( + 488448F0297CD209005B8A24 /* ImageButton.swift */, + ); + path = Button; + sourceTree = ""; + }; + 488448F3297CDF4B005B8A24 /* TextField */ = { + isa = PBXGroup; + children = ( + 488448E2297B8F1B005B8A24 /* DefaultTextFieldStyle.swift */, + ); + path = TextField; + sourceTree = ""; + }; + 488448F4297CDF54005B8A24 /* Bar */ = { + isa = PBXGroup; + children = ( + 488448F5297CDF67005B8A24 /* SideMenu.swift */, + ); + path = Bar; + sourceTree = ""; + }; + 488448F7297CDFB1005B8A24 /* Features */ = { + isa = PBXGroup; + children = ( + 488448EB297CAC28005B8A24 /* Completion */, + ); + path = Features; + sourceTree = ""; + }; + 488448F8297CDFC6005B8A24 /* Model */ = { + isa = PBXGroup; + children = ( + 488448EC297CAC4F005B8A24 /* ChatMessage.swift */, + ); + path = Model; + sourceTree = ""; + }; + 488448F9297CDFD0005B8A24 /* ViewModel */ = { + isa = PBXGroup; + children = ( + 488448D8297B8419005B8A24 /* CompletionViewModel.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; + 488448FA297CDFF2005B8A24 /* Presenter */ = { + isa = PBXGroup; + children = ( + 4884490B297CE1DE005B8A24 /* Main */, + 48844906297CE0D4005B8A24 /* Splash */, + ); + path = Presenter; + sourceTree = ""; + }; + 488448FD297CE019005B8A24 /* Router */ = { + isa = PBXGroup; + children = ( + 488448FE297CE035005B8A24 /* AppRouter.swift */, + 48844909297CE1C5005B8A24 /* MainRouter.swift */, + ); + path = Router; + sourceTree = ""; + }; + 48844900297CE062005B8A24 /* Core */ = { + isa = PBXGroup; + children = ( + 489A546B297F93EF00A6532C /* Env */, + 48844901297CE06E005B8A24 /* Navigation */, + ); + path = Core; + sourceTree = ""; + }; + 48844901297CE06E005B8A24 /* Navigation */ = { + isa = PBXGroup; + children = ( + 48844902297CE07C005B8A24 /* NavGraph.swift */, + 48844904297CE094005B8A24 /* TransitionAnimation.swift */, + ); + path = Navigation; + sourceTree = ""; + }; + 48844906297CE0D4005B8A24 /* Splash */ = { + isa = PBXGroup; + children = ( + 488448FB297CE004005B8A24 /* SplashView.swift */, + ); + path = Splash; + sourceTree = ""; + }; + 4884490B297CE1DE005B8A24 /* Main */ = { + isa = PBXGroup; + children = ( + 4884490C297CE1E6005B8A24 /* MainView.swift */, + ); + path = Main; + sourceTree = ""; + }; + 489A546B297F93EF00A6532C /* Env */ = { + isa = PBXGroup; + children = ( + 489A546C297F93F700A6532C /* Config.swift */, + ); + path = Env; + sourceTree = ""; + }; 7555FF72242A565900829871 = { isa = PBXGroup; children = ( 7555FF7D242A565900829871 /* ios-sample */, 7555FF7C242A565900829871 /* Products */, 7555FFB0242A642200829871 /* Frameworks */, + 489A546A297F916C00A6532C /* EnvConfig.xcconfig */, + 489A5471297F99FC00A6532C /* README.md */, ); sourceTree = ""; }; @@ -74,11 +282,13 @@ 7555FF7D242A565900829871 /* ios-sample */ = { isa = PBXGroup; children = ( + 488448DA297B8D19005B8A24 /* App */, + 488448F7297CDFB1005B8A24 /* Features */, + 488448DB297B8D57005B8A24 /* UI */, + 48844900297CE062005B8A24 /* Core */, + 058557D7273AAEEB004C7B11 /* Preview Content */, 058557BA273AAA24004C7B11 /* Assets.xcassets */, - 7555FF82242A565900829871 /* ContentView.swift */, 7555FF8C242A565B00829871 /* Info.plist */, - 2152FB032600AC8F00CF470E /* iOSApp.swift */, - 058557D7273AAEEB004C7B11 /* Preview Content */, ); path = "ios-sample"; sourceTree = ""; @@ -182,8 +392,26 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 48844903297CE07C005B8A24 /* NavGraph.swift in Sources */, + 488448DF297B8DF6005B8A24 /* Typography.swift in Sources */, + 488448E3297B8F1B005B8A24 /* DefaultTextFieldStyle.swift in Sources */, + 488448D9297B8419005B8A24 /* CompletionViewModel.swift in Sources */, + 48844905297CE094005B8A24 /* TransitionAnimation.swift in Sources */, + 4884490D297CE1E6005B8A24 /* MainView.swift in Sources */, + 488448ED297CAC4F005B8A24 /* ChatMessage.swift in Sources */, + 488448EA297B9843005B8A24 /* Icon.swift in Sources */, + 488448DD297B8DD2005B8A24 /* Color.swift in Sources */, + 488448EF297CD1E8005B8A24 /* ToolbarModifer.swift in Sources */, + 488448F1297CD209005B8A24 /* ImageButton.swift in Sources */, + 488448FF297CE035005B8A24 /* AppRouter.swift in Sources */, + 488448F6297CDF67005B8A24 /* SideMenu.swift in Sources */, 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */, - 7555FF83242A565900829871 /* ContentView.swift in Sources */, + 488448E8297B9198005B8A24 /* ViewModifer.swift in Sources */, + 7555FF83242A565900829871 /* CompletionView.swift in Sources */, + 488448E6297B9116005B8A24 /* RoundedCorner.swift in Sources */, + 489A546D297F93F700A6532C /* Config.swift in Sources */, + 488448FC297CE004005B8A24 /* SplashView.swift in Sources */, + 4884490A297CE1C5005B8A24 /* MainRouter.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -192,6 +420,7 @@ /* Begin XCBuildConfiguration section */ 7555FFA3242A565B00829871 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 489A546A297F916C00A6532C /* EnvConfig.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -241,7 +470,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.1; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -253,6 +482,7 @@ }; 7555FFA4242A565B00829871 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 489A546A297F916C00A6532C /* EnvConfig.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -296,7 +526,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.1; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; @@ -308,6 +538,7 @@ }; 7555FFA6242A565B00829871 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 489A546A297F916C00A6532C /* EnvConfig.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; @@ -329,6 +560,7 @@ }; 7555FFA7242A565B00829871 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 489A546A297F916C00A6532C /* EnvConfig.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; diff --git a/ios-sample/ios-sample/App/Presenter/Main/MainView.swift b/ios-sample/ios-sample/App/Presenter/Main/MainView.swift new file mode 100644 index 0000000..745073d --- /dev/null +++ b/ios-sample/ios-sample/App/Presenter/Main/MainView.swift @@ -0,0 +1,126 @@ +// +// MainView.swift +// ios-sample +// +// Created by Koji Osugi on 22/01/23. +// Copyright © 2023 orgName. All rights reserved. +// + +import SwiftUI + +internal struct MainView: View { + + @ObservedObject + private var mainRouter: MainRouter = MainRouter.shared + + @State private var showSidebar: Bool = false + + private var topBarTitle: String { + switch mainRouter.navGraph.destination { + case .completion: return "Completion" + } + } + + var body: some View { + NavigationStack { + ZStack() { + VStack(alignment: .leading) { + TopBar() + Group { + switch mainRouter.navGraph.destination { + case .completion: CompletionView() + } + } + .transition(mainRouter.navGraph.animation) + .animation(.default, value: mainRouter.navGraph.destination) + } + .fullScreen() + SideMenu(isVisible: $showSidebar) { + SideMenuContent() + } + } + } + } + + @ViewBuilder + private func TopBar() -> some View { + HStack(spacing: 0) { + ImageButton( + .menu, + color: .accentColor, + action: { showSidebar.toggle() } + ) + Text(topBarTitle) + .style(.title) + .padding(.leading, 16) + Spacer() + } + .padding(16) + .frame(maxWidth: .infinity) + .compositingGroup() + } + + @ViewBuilder + private func SideMenuContent() -> some View { + VStack(alignment: .leading, spacing: 0) { + HStack(spacing: 8) { + Image(uiImage: .logo) + Text("YChat GPT") + .style(.bodyBold) + } + .padding(.horizontal, 16) + .padding(.vertical, 16) + Divider() + .padding(.bottom, 16) + MenuItem( + icon: .chat, + text: "Completion", + destination: .completion + ) + } + .frame( + maxWidth: .infinity, + maxHeight: .infinity, + alignment: .topLeading + ) + } + + @ViewBuilder + private func MenuItem( + icon: UIImage, + text: String, + destination: MainRouter.Destination + ) -> some View { + let isSelected: Bool = mainRouter.navGraph.destination == destination + let bgColor = isSelected ? Color.primaryExtraLight : .background + let foregroundColor = isSelected ? Color.accentColor : .grayDark + Button( + action: { + mainRouter.replace(destination) + showSidebar.toggle() + } + ) { + HStack(spacing: 8) { + Image(uiImage: icon) + .renderingMode(.template) + .foregroundColor(foregroundColor) + Text(text) + .foregroundColor(foregroundColor) + .style(.caption) + Spacer() + } + .padding(.vertical, 12) + .padding(.horizontal, 16) + .frame(maxWidth: .infinity) + .background(bgColor) + .cornerRadius(20, corners: [.topRight, .bottomRight]) + .padding(.trailing, 16) + } + } +} + +internal struct MainView_Previews: PreviewProvider { + static var previews: some View { + MainView() + } +} diff --git a/ios-sample/ios-sample/App/Presenter/Splash/SplashView.swift b/ios-sample/ios-sample/App/Presenter/Splash/SplashView.swift new file mode 100644 index 0000000..d16a469 --- /dev/null +++ b/ios-sample/ios-sample/App/Presenter/Splash/SplashView.swift @@ -0,0 +1,43 @@ +// +// SplashView.swift +// ios-sample +// +// Created by Koji Osugi on 22/01/23. +// Copyright © 2023 orgName. All rights reserved. +// + +import SwiftUI + +internal struct SplashView: View { + + private let appRouter: AppRouter + + init(appRouter: AppRouter = AppRouter.shared) { + self.appRouter = appRouter + } + + var body: some View { + VStack { + HStack(spacing: 8) { + Image(uiImage: .logoBig) + Text("YChat GPT") + .font(.system(size: 24)) + .foregroundColor(.grayDark) + .bold() + } + } + .fullScreen(alignment: .center) + .onAppear { + DispatchQueue.main.asyncAfter(deadline: .now() + 2) { + appRouter.push(.main) + } + } + } +} + +internal struct SplashView_Previews: PreviewProvider { + static var previews: some View { + SplashView() + } +} + diff --git a/ios-sample/ios-sample/App/Router/AppRouter.swift b/ios-sample/ios-sample/App/Router/AppRouter.swift new file mode 100644 index 0000000..6ba9cff --- /dev/null +++ b/ios-sample/ios-sample/App/Router/AppRouter.swift @@ -0,0 +1,27 @@ +// +// AppRouter.swift +// ios-sample +// +// Created by Koji Osugi on 22/01/23. +// Copyright © 2023 orgName. All rights reserved. +// + +import Foundation + +internal final class AppRouter: ObservableObject { + + static var shared = AppRouter() + + private init() {} + + enum Destination: Equatable { + case splash + case main + } + + @Published var navGraph: NavGraph = .init(destination: .splash) + + func push(_ destination: Destination) { + navGraph.push(destination: destination) + } +} diff --git a/ios-sample/ios-sample/App/Router/MainRouter.swift b/ios-sample/ios-sample/App/Router/MainRouter.swift new file mode 100644 index 0000000..d237877 --- /dev/null +++ b/ios-sample/ios-sample/App/Router/MainRouter.swift @@ -0,0 +1,26 @@ +// +// MainRouter.swift +// ios-sample +// +// Created by Koji Osugi on 22/01/23. +// Copyright © 2023 orgName. All rights reserved. +// + +import Foundation + +internal final class MainRouter: ObservableObject { + + static var shared = MainRouter() + + private init() {} + + enum Destination: Equatable { + case completion + } + + @Published var navGraph: NavGraph = .init(destination: .completion) + + func replace(_ destination: Destination) { + navGraph.replace(destination, animation: .opacity) + } +} diff --git a/ios-sample/ios-sample/App/iOSApp.swift b/ios-sample/ios-sample/App/iOSApp.swift new file mode 100644 index 0000000..b9c5f36 --- /dev/null +++ b/ios-sample/ios-sample/App/iOSApp.swift @@ -0,0 +1,21 @@ +import SwiftUI + +@main +struct iOSApp: App { + + @ObservedObject + private var appRouter: AppRouter = AppRouter.shared + + var body: some Scene { + WindowGroup { + Group { + switch appRouter.navGraph.destination { + case .splash: SplashView() + case .main: MainView() + } + } + .transition(appRouter.navGraph.animation) + .animation(.default, value: appRouter.navGraph.destination) + } + } +} diff --git a/ios-sample/ios-sample/Assets.xcassets/Colors/Accent/AccentMain.colorset/Contents.json b/ios-sample/ios-sample/Assets.xcassets/Colors/Accent/AccentMain.colorset/Contents.json new file mode 100644 index 0000000..7926701 --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Colors/Accent/AccentMain.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0x8A", + "red" : "0x44" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios-sample/ios-sample/Assets.xcassets/Colors/Accent/Contents.json b/ios-sample/ios-sample/Assets.xcassets/Colors/Accent/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Colors/Accent/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios-sample/ios-sample/Assets.xcassets/Colors/Accent/OnAccentMain.colorset/Contents.json b/ios-sample/ios-sample/Assets.xcassets/Colors/Accent/OnAccentMain.colorset/Contents.json new file mode 100644 index 0000000..fafa476 --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Colors/Accent/OnAccentMain.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios-sample/ios-sample/Assets.xcassets/Colors/Contents.json b/ios-sample/ios-sample/Assets.xcassets/Colors/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Colors/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios-sample/ios-sample/Assets.xcassets/Colors/Gray/Contents.json b/ios-sample/ios-sample/Assets.xcassets/Colors/Gray/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Colors/Gray/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios-sample/ios-sample/Assets.xcassets/Colors/Gray/GrayDark.colorset/Contents.json b/ios-sample/ios-sample/Assets.xcassets/Colors/Gray/GrayDark.colorset/Contents.json new file mode 100644 index 0000000..5c9b7b9 --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Colors/Gray/GrayDark.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x21", + "green" : "0x21", + "red" : "0x21" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios-sample/ios-sample/Assets.xcassets/Colors/Gray/GrayExtraLight.colorset/Contents.json b/ios-sample/ios-sample/Assets.xcassets/Colors/Gray/GrayExtraLight.colorset/Contents.json new file mode 100644 index 0000000..ae8dde8 --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Colors/Gray/GrayExtraLight.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF5", + "green" : "0xF5", + "red" : "0xF5" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios-sample/ios-sample/Assets.xcassets/Colors/Gray/GrayLight.colorset/Contents.json b/ios-sample/ios-sample/Assets.xcassets/Colors/Gray/GrayLight.colorset/Contents.json new file mode 100644 index 0000000..e7d0ceb --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Colors/Gray/GrayLight.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xE0", + "green" : "0xE0", + "red" : "0xE0" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios-sample/ios-sample/Assets.xcassets/Colors/Gray/GrayMain.colorset/Contents.json b/ios-sample/ios-sample/Assets.xcassets/Colors/Gray/GrayMain.colorset/Contents.json new file mode 100644 index 0000000..4043276 --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Colors/Gray/GrayMain.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x42", + "green" : "0x42", + "red" : "0x42" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios-sample/ios-sample/Assets.xcassets/Colors/Gray/GrayMedium.colorset/Contents.json b/ios-sample/ios-sample/Assets.xcassets/Colors/Gray/GrayMedium.colorset/Contents.json new file mode 100644 index 0000000..d830559 --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Colors/Gray/GrayMedium.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x9E", + "green" : "0x9E", + "red" : "0x9E" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios-sample/ios-sample/Assets.xcassets/Colors/Primary/Contents.json b/ios-sample/ios-sample/Assets.xcassets/Colors/Primary/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Colors/Primary/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios-sample/ios-sample/Assets.xcassets/Colors/Primary/PrimaryDark.colorset/Contents.json b/ios-sample/ios-sample/Assets.xcassets/Colors/Primary/PrimaryDark.colorset/Contents.json new file mode 100644 index 0000000..63fabec --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Colors/Primary/PrimaryDark.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xA1", + "green" : "0x47", + "red" : "0x0D" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios-sample/ios-sample/Assets.xcassets/Colors/Primary/PrimaryExtraLight.colorset/Contents.json b/ios-sample/ios-sample/Assets.xcassets/Colors/Primary/PrimaryExtraLight.colorset/Contents.json new file mode 100644 index 0000000..b5f37f9 --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Colors/Primary/PrimaryExtraLight.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFD", + "green" : "0xF2", + "red" : "0xE3" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios-sample/ios-sample/Assets.xcassets/Colors/Primary/PrimaryLight.colorset/Contents.json b/ios-sample/ios-sample/Assets.xcassets/Colors/Primary/PrimaryLight.colorset/Contents.json new file mode 100644 index 0000000..bf78d9e --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Colors/Primary/PrimaryLight.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFB", + "green" : "0xDE", + "red" : "0xBB" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios-sample/ios-sample/Assets.xcassets/Colors/Primary/PrimaryMain.colorset/Contents.json b/ios-sample/ios-sample/Assets.xcassets/Colors/Primary/PrimaryMain.colorset/Contents.json new file mode 100644 index 0000000..edabd9c --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Colors/Primary/PrimaryMain.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF3", + "green" : "0x96", + "red" : "0x21" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios-sample/ios-sample/Assets.xcassets/Colors/Primary/PrimaryMedium.colorset/Contents.json b/ios-sample/ios-sample/Assets.xcassets/Colors/Primary/PrimaryMedium.colorset/Contents.json new file mode 100644 index 0000000..5ea42e2 --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Colors/Primary/PrimaryMedium.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF6", + "green" : "0xB5", + "red" : "0x64" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios-sample/ios-sample/Assets.xcassets/Images/Contents.json b/ios-sample/ios-sample/Assets.xcassets/Images/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Images/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios-sample/ios-sample/Assets.xcassets/Images/Icons/Contents.json b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_arrow_down.imageset/Contents.json b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_arrow_down.imageset/Contents.json new file mode 100644 index 0000000..1f8497b --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_arrow_down.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "ic_arrow_down.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_arrow_down.imageset/ic_arrow_down.svg b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_arrow_down.imageset/ic_arrow_down.svg new file mode 100644 index 0000000..7ee526f --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_arrow_down.imageset/ic_arrow_down.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_arrow_left.imageset/Contents.json b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_arrow_left.imageset/Contents.json new file mode 100644 index 0000000..77944d7 --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_arrow_left.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "ic_arrow_left.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_arrow_left.imageset/ic_arrow_left.svg b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_arrow_left.imageset/ic_arrow_left.svg new file mode 100644 index 0000000..497ce69 --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_arrow_left.imageset/ic_arrow_left.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_bot.imageset/Contents.json b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_bot.imageset/Contents.json new file mode 100644 index 0000000..3d0a3de --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_bot.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "ic_bot.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_bot.imageset/ic_bot.svg b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_bot.imageset/ic_bot.svg new file mode 100644 index 0000000..e1fdebc --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_bot.imageset/ic_bot.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_chat.imageset/Chat.svg b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_chat.imageset/Chat.svg new file mode 100644 index 0000000..490126c --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_chat.imageset/Chat.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_chat.imageset/Contents.json b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_chat.imageset/Contents.json new file mode 100644 index 0000000..cb85785 --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_chat.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "Chat.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_edit.imageset/Contents.json b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_edit.imageset/Contents.json new file mode 100644 index 0000000..de2fb23 --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_edit.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "Edit.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_edit.imageset/Edit.svg b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_edit.imageset/Edit.svg new file mode 100644 index 0000000..c6fa3dc --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_edit.imageset/Edit.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_logo.imageset/Contents.json b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_logo.imageset/Contents.json new file mode 100644 index 0000000..2b18e1e --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_logo.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "ic_logo.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_logo.imageset/ic_logo.svg b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_logo.imageset/ic_logo.svg new file mode 100644 index 0000000..bc47130 --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_logo.imageset/ic_logo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_logo_big.imageset/Contents.json b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_logo_big.imageset/Contents.json new file mode 100644 index 0000000..0d38bd0 --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_logo_big.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "ic_logo_big.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_logo_big.imageset/ic_logo_big.svg b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_logo_big.imageset/ic_logo_big.svg new file mode 100644 index 0000000..4ba5a03 --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_logo_big.imageset/ic_logo_big.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_menu.imageset/Contents.json b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_menu.imageset/Contents.json new file mode 100644 index 0000000..7ff1ce3 --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_menu.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "ic_menu.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_menu.imageset/ic_menu.svg b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_menu.imageset/ic_menu.svg new file mode 100644 index 0000000..c4304b7 --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_menu.imageset/ic_menu.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/ios-sample/ios-sample/Assets.xcassets/AccentColor.colorset/Contents.json b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_refresh.imageset/Contents.json similarity index 66% rename from ios-sample/ios-sample/Assets.xcassets/AccentColor.colorset/Contents.json rename to ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_refresh.imageset/Contents.json index ee7e3ca..8bf86d9 100644 --- a/ios-sample/ios-sample/Assets.xcassets/AccentColor.colorset/Contents.json +++ b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_refresh.imageset/Contents.json @@ -1,6 +1,7 @@ { - "colors" : [ + "images" : [ { + "filename" : "ic_refresh.svg", "idiom" : "universal" } ], @@ -8,4 +9,4 @@ "author" : "xcode", "version" : 1 } -} \ No newline at end of file +} diff --git a/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_refresh.imageset/ic_refresh.svg b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_refresh.imageset/ic_refresh.svg new file mode 100644 index 0000000..f813451 --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_refresh.imageset/ic_refresh.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_send.imageset/Contents.json b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_send.imageset/Contents.json new file mode 100644 index 0000000..5b21110 --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_send.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "ic_send.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_send.imageset/ic_send.svg b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_send.imageset/ic_send.svg new file mode 100644 index 0000000..bfd2602 --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_send.imageset/ic_send.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_settings.imageset/Contents.json b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_settings.imageset/Contents.json new file mode 100644 index 0000000..a8fc327 --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_settings.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "ic_settings.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_settings.imageset/ic_settings.svg b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_settings.imageset/ic_settings.svg new file mode 100644 index 0000000..da49ebf --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_settings.imageset/ic_settings.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_swap.imageset/Contents.json b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_swap.imageset/Contents.json new file mode 100644 index 0000000..e039768 --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_swap.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "ic_swap.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_swap.imageset/ic_swap.svg b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_swap.imageset/ic_swap.svg new file mode 100644 index 0000000..ee58cf3 --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_swap.imageset/ic_swap.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_warning_outline.imageset/Contents.json b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_warning_outline.imageset/Contents.json new file mode 100644 index 0000000..274230c --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_warning_outline.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "Warning Outline.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_warning_outline.imageset/Warning Outline.svg b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_warning_outline.imageset/Warning Outline.svg new file mode 100644 index 0000000..d10a447 --- /dev/null +++ b/ios-sample/ios-sample/Assets.xcassets/Images/Icons/ic_warning_outline.imageset/Warning Outline.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/ios-sample/ios-sample/ContentView.swift b/ios-sample/ios-sample/ContentView.swift deleted file mode 100644 index 3a83077..0000000 --- a/ios-sample/ios-sample/ContentView.swift +++ /dev/null @@ -1,14 +0,0 @@ -import SwiftUI - -struct ContentView: View { - - var body: some View { - Text("Hello world!") - } -} - -struct ContentView_Previews: PreviewProvider { - static var previews: some View { - ContentView() - } -} diff --git a/ios-sample/ios-sample/Core/Env/Config.swift b/ios-sample/ios-sample/Core/Env/Config.swift new file mode 100644 index 0000000..d96f0c7 --- /dev/null +++ b/ios-sample/ios-sample/Core/Env/Config.swift @@ -0,0 +1,20 @@ +// +// Config.swift +// ios-sample +// +// Created by Koji Osugi on 24/01/23. +// Copyright © 2023 orgName. All rights reserved. +// + +import Foundation + +enum Config { + + static var apiKey: String { + string(for: "API_KEY") + } + + private static func string(for key: String) -> String { + Bundle.main.infoDictionary?[key] as! String + } +} diff --git a/ios-sample/ios-sample/Core/Environment/EnvConstant.swift b/ios-sample/ios-sample/Core/Environment/EnvConstant.swift new file mode 100644 index 0000000..7663d97 --- /dev/null +++ b/ios-sample/ios-sample/Core/Environment/EnvConstant.swift @@ -0,0 +1,9 @@ +// +// EnvConstant.swift +// ios-sample +// +// Created by Koji Osugi on 22/01/23. +// Copyright © 2023 orgName. All rights reserved. +// + +import Foundation diff --git a/ios-sample/ios-sample/Core/Navigation/NavGraph.swift b/ios-sample/ios-sample/Core/Navigation/NavGraph.swift new file mode 100644 index 0000000..d4f9d12 --- /dev/null +++ b/ios-sample/ios-sample/Core/Navigation/NavGraph.swift @@ -0,0 +1,41 @@ +// +// NavGraph.swift +// ios-sample +// +// Created by Koji Osugi on 22/01/23. +// Copyright © 2023 orgName. All rights reserved. +// + +import SwiftUI + +struct NavGraph { + var navStack: [T] = [] + var destination: T + var animation: AnyTransition + + init(destination: T) { + self.animation = .nextSlide + self.navStack.append(destination) + self.destination = destination + } + + mutating func replace(_ destination: T, animation: AnyTransition = .nextSlide) { + self.animation = animation + let _ = navStack.popLast() + self.navStack.append(destination) + self.destination = destination + } + + mutating func push(destination: T) { + self.animation = .nextSlide + self.navStack.append(destination) + self.destination = destination + } + + mutating func pop() { + self.animation = .backSlide + let _ = navStack.popLast() + guard let lastDestination = navStack.last else { return } + self.destination = lastDestination + } +} diff --git a/ios-sample/ios-sample/Core/Navigation/TransitionAnimation.swift b/ios-sample/ios-sample/Core/Navigation/TransitionAnimation.swift new file mode 100644 index 0000000..5016443 --- /dev/null +++ b/ios-sample/ios-sample/Core/Navigation/TransitionAnimation.swift @@ -0,0 +1,26 @@ +// +// TransitionAnimation.swift +// ios-sample +// +// Created by Koji Osugi on 22/01/23. +// Copyright © 2023 orgName. All rights reserved. +// + +import SwiftUI + +extension AnyTransition { + + static var backSlide: AnyTransition { + AnyTransition.asymmetric( + insertion: .move(edge: .leading), + removal: .move(edge: .trailing) + ) + } + + static var nextSlide: AnyTransition { + AnyTransition.asymmetric( + insertion: .move(edge: .trailing), + removal: .move(edge: .leading) + ) + } +} diff --git a/ios-sample/ios-sample/Features/Completion/CompletionView.swift b/ios-sample/ios-sample/Features/Completion/CompletionView.swift new file mode 100644 index 0000000..c2cdb83 --- /dev/null +++ b/ios-sample/ios-sample/Features/Completion/CompletionView.swift @@ -0,0 +1,194 @@ +import SwiftUI + +struct CompletionView: View { + + @ObservedObject + private var viewModel: CompletionViewModel + + private var enableButton: Bool { + !viewModel.isLoading && !viewModel.message.isEmpty + } + + init(viewModel: CompletionViewModel = .init()) { + self.viewModel = viewModel + } + + var body: some View { + VStack { + if (viewModel.chatMessageList.isEmpty) { + EmptyMessage() + .padding(.top, 16) + } else { + ScrollViewReader { value in + ScrollView { + ForEach(viewModel.chatMessageList) { + ChatBubble(chatMessage: $0) + .padding(.top, 16) + .id($0.id) + } + .padding(.horizontal, 8) + } + .onChange(of: viewModel.chatMessageList) { + value.scrollTo($0.last?.id, anchor: .bottom) + } + } + } + Spacer() + SendMessageSection() + .padding(.horizontal, 12) + .padding(.vertical, 12) + }.fullScreen() + } + + @ViewBuilder + private func EmptyMessage() -> some View { + ZStack { + Text("Enter any message and chat GPT will answer it.") + .foregroundColor(.grayMain) + .style(.caption) + .bold() + } + .padding(.horizontal, 16) + .padding(.vertical, 8) + .background(Color.grayLight) + .cornerRadius(16) + .padding(.horizontal, 32) + } + + @ViewBuilder + private func ChatBubble(chatMessage: ChatMessage) -> some View { + switch chatMessage.type { + case .human(let error): + HStack(spacing: 4) { + Spacer() + Spacer().frame(width: 60) + HumanChatBubble(message: chatMessage.message) + if error { + Image(uiImage: .warningOutline) + .renderingMode(.template) + .foregroundColor(.red) + } + } + case .ai: + HStack { + AIChatBubble(message: chatMessage.message) + Spacer().frame(width: 60) + Spacer() + } + case .loading: + HStack { + TypingLoading() + Spacer() + } + } + } + + @ViewBuilder + private func HumanChatBubble(message: String) -> some View { + ZStack() { + Text(message) + .foregroundColor(.white) + .style(.body) + } + .padding(.horizontal, 16) + .padding(.vertical, 8) + .background(Color.accentMain) + .cornerRadius(16, corners: [.bottomLeft, .topLeft, .topRight]) + } + + @ViewBuilder + private func AIChatBubble(message: String) -> some View { + HStack(alignment: .top, spacing: 4) { + Circle() + .fill(.green) + .frame(width: 40, height: 40) + .overlay { + Image(uiImage: .bot) + .renderingMode(.template) + .foregroundColor(.white) + } + ZStack() { + Text(message) + .foregroundColor(.grayDark) + .style(.body) + .multilineTextAlignment(.leading) + } + .padding(.horizontal, 16) + .padding(.vertical, 8) + .background(Color.grayLight) + .cornerRadius(16, corners: [.bottomLeft, .bottomLeft, .topRight]) + } + } + + @ViewBuilder + private func SendMessageSection() -> some View { + HStack(spacing: 8) { + TextField(text: $viewModel.message) { + Text("Message") + .foregroundColor(.grayMedium) + .style(.subtitle) + } + .textFieldStyle(DefaultTextFieldStyle()) + .disabled(viewModel.isLoading) + .opacity(viewModel.isLoading ? 0.4 : 1) + SendButton() + } + } + + @ViewBuilder + private func SendButton() -> some View { + Button(action: { viewModel.sendMessage() }) { + Circle() + .fill(enableButton ? Color.accentMain : .grayLight) + .frame(width: 40, height: 40) + .overlay { + Image(uiImage: .send) + .renderingMode(.template) + .foregroundColor(.white) + } + } + .disabled(viewModel.message.isEmpty) + } +} + +private struct TypingLoading: View { + + @State + private var typingState = "Typing." + + @State + private var timer: Timer? + + var body: some View { + ZStack() { + Text(typingState) + .foregroundColor(.grayMedium) + .style(.body) + } + .padding(.horizontal, 16) + .padding(.vertical, 8) + .background(Color.grayLight) + .cornerRadius(16, corners: [.bottomLeft, .bottomLeft, .topRight]) + .onAppear { + timer = Timer.scheduledTimer(withTimeInterval: 0.25, repeats: true) { _ in + switch typingState { + case "Typing...": typingState = "Typing." + case "Typing.": typingState = "Typing.." + default: typingState = "Typing..." + } + } + } + .onDisappear { + timer?.invalidate() + } + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + NavigationStack { + CompletionView() + .applyToolbar("Completion") + } + } +} diff --git a/ios-sample/ios-sample/Features/Completion/Model/ChatMessage.swift b/ios-sample/ios-sample/Features/Completion/Model/ChatMessage.swift new file mode 100644 index 0000000..154a82e --- /dev/null +++ b/ios-sample/ios-sample/Features/Completion/Model/ChatMessage.swift @@ -0,0 +1,19 @@ +// +// ChatMessage.swift +// ios-sample +// +// Created by Koji Osugi on 21/01/23. +// Copyright © 2023 orgName. All rights reserved. +// + +import Foundation + +struct ChatMessage: Identifiable, Equatable { + let id: String + var message: String = "" + var type: MessageType = .human(error: false) + + enum MessageType: Equatable { + case human(error: Bool), ai, loading + } +} diff --git a/ios-sample/ios-sample/Features/Completion/ViewModel/CompletionViewModel.swift b/ios-sample/ios-sample/Features/Completion/ViewModel/CompletionViewModel.swift new file mode 100644 index 0000000..db586ae --- /dev/null +++ b/ios-sample/ios-sample/Features/Completion/ViewModel/CompletionViewModel.swift @@ -0,0 +1,86 @@ +// +// CompletionViewModel.swift +// ios-sample +// +// Created by Koji Osugi on 20/01/23. +// Copyright © 2023 orgName. All rights reserved. +// + +import Foundation +import chat_gpt_sdk + +internal final class CompletionViewModel: ObservableObject { + + private var chatGpt: ChatGpt { + ChatGptCompanion.shared.create(apiKey: Config.apiKey) + } + + @Published + var message: String = "" + + @Published + var chatMessageList: [ChatMessage] = [] + + var isLoading: Bool { + chatMessageList.contains { $0.type == .loading } + } + + @MainActor + func sendMessage() { + Task.init { + let input = message + addHumanMessage(message: message) + cleanLastMessage() + addLoading() + do { + let result = try await chatGpt.completion(input: input) + removeLoading() + addAIMessage(message: result) + } catch { + removeLoading() + setError() + } + } + } + + private func addHumanMessage(message: String) { + let chatMessage = ChatMessage( + id: UUID().uuidString, + message: message, + type: .human(error: false) + ) + chatMessageList.append(chatMessage) + } + + private func addAIMessage(message: String) { + let chatMessage = ChatMessage( + id: UUID().uuidString, + message: message, + type: .ai + ) + chatMessageList.append(chatMessage) + } + + private func addLoading() { + let chatMessage = ChatMessage( + id: UUID().uuidString, + type: .loading + ) + chatMessageList.append(chatMessage) + } + + private func removeLoading() { + chatMessageList.removeAll { $0.type == .loading } + } + + private func cleanLastMessage() { + message = "" + chatMessageList.removeAll(where: { $0.type == .human(error: true) }) + } + + private func setError() { + if let row = self.chatMessageList.lastIndex(where: { $0.type == .human(error: false) }) { + chatMessageList[row].type = .human(error: true) + } + } +} diff --git a/ios-sample/ios-sample/Info.plist b/ios-sample/ios-sample/Info.plist index 8044709..17a0b9c 100644 --- a/ios-sample/ios-sample/Info.plist +++ b/ios-sample/ios-sample/Info.plist @@ -2,6 +2,8 @@ + API_KEY + $(API_KEY) CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable @@ -25,6 +27,8 @@ UIApplicationSupportsMultipleScenes + UILaunchScreen + UIRequiredDeviceCapabilities armv7 @@ -42,7 +46,5 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - UILaunchScreen - - \ No newline at end of file + diff --git a/ios-sample/ios-sample/UI/Component/Bar/SideMenu.swift b/ios-sample/ios-sample/UI/Component/Bar/SideMenu.swift new file mode 100644 index 0000000..5faa75f --- /dev/null +++ b/ios-sample/ios-sample/UI/Component/Bar/SideMenu.swift @@ -0,0 +1,67 @@ +// +// SideMenu.swift +// ios-sample +// +// Created by Koji Osugi on 22/01/23. +// Copyright © 2023 orgName. All rights reserved. +// + +import SwiftUI + +struct SideMenu: View { + + @Binding private var isVisible: Bool + private let width: CGFloat + private let bgColor: Color + private let content: SidebarContent + + init( + isVisible: Binding, + width: CGFloat = UIScreen.main.bounds.size.width * 0.7, + bgColor: Color = .background, + @ViewBuilder content: () -> SidebarContent + ) { + self._isVisible = isVisible + self.width = width + self.bgColor = bgColor + self.content = content() + } + + var body: some View { + ZStack { + GeometryReader { _ in + EmptyView() + } + .background(.black.opacity(0.6)) + .opacity(isVisible ? 1 : 0) + .animation(.easeInOut.delay(0.2), value: isVisible) + .onTapGesture { + isVisible.toggle() + } + SideMenuStructure() + } + .edgesIgnoringSafeArea(.all) + } + + @ViewBuilder + private func SideMenuStructure() -> some View { + HStack(alignment: .top) { + ZStack(alignment: .top) { + bgColor + content + } + .frame(width: width) + .offset(x: isVisible ? 0 : -width) + .animation(.default, value: isVisible) + Spacer() + } + } +} + +struct SideMenu_Previews: PreviewProvider { + static var previews: some View { + SideMenu(isVisible: .constant(true)) { + Text("Side Menu") + } + } +} diff --git a/ios-sample/ios-sample/UI/Component/Button/ImageButton.swift b/ios-sample/ios-sample/UI/Component/Button/ImageButton.swift new file mode 100644 index 0000000..4943d65 --- /dev/null +++ b/ios-sample/ios-sample/UI/Component/Button/ImageButton.swift @@ -0,0 +1,44 @@ +// +// ImageButton.swift +// ios-sample +// +// Created by Koji Osugi on 21/01/23. +// Copyright © 2023 orgName. All rights reserved. +// + +import SwiftUI + +struct ImageButton: View { + + private let icon: UIImage + private var color: Color? + private let action: () -> Void + + init( + _ icon: UIImage, + color: Color? = nil, + action: @escaping () -> Void = {} + ) { + self.icon = icon + self.color = color + self.action = action + } + + var body: some View { + Button(action: action) { + if let color = color { + Image(uiImage: icon) + .renderingMode(.template) + .foregroundColor(color) + } else { + Image(uiImage: icon) + } + } + } +} + +struct ImageButton_Previews: PreviewProvider { + static var previews: some View { + ImageButton(.menu) + } +} diff --git a/ios-sample/ios-sample/UI/Component/TextField/DefaultTextFieldStyle.swift b/ios-sample/ios-sample/UI/Component/TextField/DefaultTextFieldStyle.swift new file mode 100644 index 0000000..7159e4d --- /dev/null +++ b/ios-sample/ios-sample/UI/Component/TextField/DefaultTextFieldStyle.swift @@ -0,0 +1,32 @@ +// +// DefaultTextFieldStyle.swift +// ios-sample +// +// Created by Koji Osugi on 21/01/23. +// Copyright © 2023 orgName. All rights reserved. +// + +import SwiftUI + +struct DefaultTextFieldStyle: TextFieldStyle { + func _body(configuration: TextField) -> some View { + configuration + .padding(.vertical) + .padding(.horizontal, 24) + .background(Color.grayExtraLight) + .foregroundColor(.grayDark) + .cornerRadius(8) + } +} + +struct DefaultTextFieldStyle_Previews: PreviewProvider { + static var previews: some View { + TextField(text: .constant("")) { + Text("Search") + .foregroundColor(.grayMedium) + .style(.subtitle) + } + .textContentType(.countryName) + .textFieldStyle(DefaultTextFieldStyle()) + } +} diff --git a/ios-sample/ios-sample/UI/Modifier/RoundedCorner.swift b/ios-sample/ios-sample/UI/Modifier/RoundedCorner.swift new file mode 100644 index 0000000..0aadb71 --- /dev/null +++ b/ios-sample/ios-sample/UI/Modifier/RoundedCorner.swift @@ -0,0 +1,26 @@ +// +// RoundedCorner.swift +// ios-sample +// +// Created by Koji Osugi on 21/01/23. +// Copyright © 2023 orgName. All rights reserved. +// + +import SwiftUI + +struct RoundedCorner: Shape { + + var radius: CGFloat = .infinity + var corners: UIRectCorner = .allCorners + + func path(in rect: CGRect) -> Path { + let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)) + return Path(path.cgPath) + } +} + +extension View { + func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View { + clipShape( RoundedCorner(radius: radius, corners: corners) ) + } +} diff --git a/ios-sample/ios-sample/UI/Modifier/ToolbarModifer.swift b/ios-sample/ios-sample/UI/Modifier/ToolbarModifer.swift new file mode 100644 index 0000000..9bfb5de --- /dev/null +++ b/ios-sample/ios-sample/UI/Modifier/ToolbarModifer.swift @@ -0,0 +1,29 @@ +// +// ToolbarModifer.swift +// ios-sample +// +// Created by Koji Osugi on 21/01/23. +// Copyright © 2023 orgName. All rights reserved. +// + +import SwiftUI + +extension View { + + func applyToolbar(_ title: String, onButtonAction: @escaping () -> Void = {}) -> some View { + self + .navigationBarBackButtonHidden(true) + .toolbar { + ToolbarItemGroup(placement: .navigationBarLeading) { + HStack { + ImageButton( + .arrowLeft, + color: .accentColor, + action: onButtonAction + ) + Text(title).style(.title) + } + } + } + } +} diff --git a/ios-sample/ios-sample/UI/Modifier/ViewModifer.swift b/ios-sample/ios-sample/UI/Modifier/ViewModifer.swift new file mode 100644 index 0000000..6e9d105 --- /dev/null +++ b/ios-sample/ios-sample/UI/Modifier/ViewModifer.swift @@ -0,0 +1,17 @@ +// +// ViewModifer.swift +// ios-sample +// +// Created by Koji Osugi on 21/01/23. +// Copyright © 2023 orgName. All rights reserved. +// + +import SwiftUI + +extension View { + + func fullScreen(alignment: Alignment = .top) -> some View { + self.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: alignment) + .background(Color.background) + } +} diff --git a/ios-sample/ios-sample/UI/Theme/Color.swift b/ios-sample/ios-sample/UI/Theme/Color.swift new file mode 100644 index 0000000..54a0a00 --- /dev/null +++ b/ios-sample/ios-sample/UI/Theme/Color.swift @@ -0,0 +1,120 @@ +// +// Color.swift +// ios-sample +// +// Created by Koji Osugi on 21/01/23. +// Copyright © 2023 orgName. All rights reserved. +// + +import SwiftUI + +extension Color { + + static var background: Color { + Color.white + } + + static var accentMain: Color { + Color("AccentMain") + } + + static var primaryDark: Color{ + Color("PrimaryDark") + } + + static var primaryMain: Color { + Color("PrimaryMain") + } + + static var primaryMedium: Color{ + Color("PrimaryMedium") + } + + static var primaryLight: Color{ + Color("PrimaryLight") + } + + static var primaryExtraLight: Color { + Color("PrimaryExtraLight") + } + + static var grayDark: Color{ + Color("GrayDark") + } + + static var grayMain: Color { + Color("GrayMain") + } + + static var grayMedium: Color{ + Color("GrayMedium") + } + + static var grayLight: Color{ + Color("GrayLight") + } + + static var grayExtraLight: Color { + Color("GrayExtraLight") + } + +} + +private struct ColorSample: View { + var body: some View { + ScrollView { + VStack(spacing: 0) { + primaryColors() + grayColors() + } + } + } + + private func primaryColors() -> some View { + Group { + Text("accentMain") + .applyStyle(Color.accentMain) + Text("primaryDark") + .applyStyle(Color.primaryDark) + Text("primaryMain") + .applyStyle(Color.primaryMain) + Text("primaryMedium") + .applyStyle(Color.primaryMedium) + Text("primaryLight") + .applyStyle(Color.primaryLight) + Text("primaryExtraLight") + .applyStyle(Color.primaryExtraLight) + } + } + + private func grayColors() -> some View { + Group { + Text("grayDark") + .applyStyle(Color.grayDark) + Text("grayMain") + .applyStyle(Color.grayMain) + Text("grayMedium") + .applyStyle(Color.grayMedium) + Text("grayLight") + .applyStyle(Color.grayLight) + Text("grayExtraLight") + .applyStyle(Color.grayExtraLight) + } + } +} + +private extension Text { + + func applyStyle(_ color: Color) -> some View { + self.frame(maxWidth: .infinity) + .padding() + .border(.black, width: 0.5) + .background(color) + } +} + +struct ColorSample_Previews: PreviewProvider { + static var previews: some View { + ColorSample() + } +} diff --git a/ios-sample/ios-sample/UI/Theme/Icon.swift b/ios-sample/ios-sample/UI/Theme/Icon.swift new file mode 100644 index 0000000..0d08b14 --- /dev/null +++ b/ios-sample/ios-sample/UI/Theme/Icon.swift @@ -0,0 +1,106 @@ +// +// Icon.swift +// ios-sample +// +// Created by Koji Osugi on 21/01/23. +// Copyright © 2023 orgName. All rights reserved. +// + +import SwiftUI + +enum Icon: String, CaseIterable { + case menu = "ic_menu" + case refresh = "ic_refresh" + case arrowLeft = "ic_arrow_left" + case arrowDown = "ic_arrow_down" + case swap = "ic_swap" + case settings = "ic_settings" + case bot = "ic_bot" + case send = "ic_send" + case chat = "ic_chat" + case edit = "ic_edit" + case warningOutline = "ic_warning_outline" + case logo = "ic_logo" + case logoBig = "ic_logo_big" +} + +extension UIImage { + + public class var logoBig: UIImage { + UIImage(named: Icon.logoBig.rawValue)! + } + + public class var logo: UIImage { + UIImage(named: Icon.logo.rawValue)! + } + + public class var warningOutline: UIImage { + UIImage(named: Icon.warningOutline.rawValue)! + } + + public class var edit: UIImage { + UIImage(named: Icon.edit.rawValue)! + } + + public class var chat: UIImage { + UIImage(named: Icon.chat.rawValue)! + } + + public class var send: UIImage { + UIImage(named: Icon.send.rawValue)! + } + + public class var bot: UIImage { + UIImage(named: Icon.bot.rawValue)! + } + + public class var settings: UIImage { + UIImage(named: Icon.settings.rawValue)! + } + + public class var swap: UIImage { + UIImage(named: Icon.swap.rawValue)! + } + + public class var menu: UIImage { + UIImage(named: Icon.menu.rawValue)! + } + + public class var refresh: UIImage { + UIImage(named: Icon.refresh.rawValue)! + } + + public class var arrowDown: UIImage { + UIImage(named: Icon.arrowDown.rawValue)! + } + + public class var arrowLeft: UIImage { + UIImage(named: Icon.arrowLeft.rawValue)! + } +} + +private struct IconSample: View { + + private let columns = [ + GridItem(.adaptive(minimum: 80)) + ] + + var body: some View { + ScrollView { + LazyVGrid(columns: columns, spacing: 8) { + ForEach(Icon.allCases, id: \.self) { + Image($0.rawValue) + .resizable() + .frame(width: 24, height: 24) + } + } + } + } +} + +internal struct IconSample_Previews: PreviewProvider { + static var previews: some View { + IconSample() + } +} + diff --git a/ios-sample/ios-sample/UI/Theme/Typography.swift b/ios-sample/ios-sample/UI/Theme/Typography.swift new file mode 100644 index 0000000..046a00a --- /dev/null +++ b/ios-sample/ios-sample/UI/Theme/Typography.swift @@ -0,0 +1,107 @@ +// +// Typography.swift +// ios-sample +// +// Created by Koji Osugi on 21/01/23. +// Copyright © 2023 orgName. All rights reserved. +// + +import SwiftUI + +enum Typography { + case title + case titleBold + case subtitle + case subtitleBold + case body + case bodyBold + case button + case caption +} + +extension Text { + + func style(_ typography: Typography) -> Text { + switch typography { + case .title: return title() + case .titleBold: return titleBold() + case .subtitle: return subtitle() + case .subtitleBold: return subtitleBold() + case .body: return body() + case .bodyBold: return bodyBold() + case .button: return button() + case .caption: return caption() + } + } + + private func title() -> Text { + self.font(.system(size: 18)) + .foregroundColor(.grayDark) + } + + private func titleBold() -> Text { + self.title() + .bold() + } + + private func subtitle() -> Text { + self.font(.system(size: 16)) + .foregroundColor(.grayDark) + } + + private func subtitleBold() -> Text { + self.subtitle() + .bold() + } + + private func body() -> Text { + self.font(.system(size: 14)) + .foregroundColor(.grayDark) + } + + private func bodyBold() -> Text { + self.body() + .bold() + } + + private func button() -> Text { + self.font(.system(size: 14)) + .foregroundColor(.grayDark) + } + + private func caption() -> Text { + self.font(.system(size: 12)) + .foregroundColor(.grayDark) + } +} + +private struct TypographySample: View { + var body: some View { + Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + } +} + +internal struct Tipography_Previews: PreviewProvider { + static var previews: some View { + ScrollView { + VStack(spacing: 16) { + Text("title") + .style(.title) + Text("titleBold") + .style(.titleBold) + Text("subtitle") + .style(.subtitle) + Text("subtitleBold") + .style(.subtitleBold) + Text("body") + .style(.body) + Text("bodyBold") + .style(.bodyBold) + Text("button") + .style(.button) + Text("caption") + .style(.caption) + } + } + } +} diff --git a/ios-sample/ios-sample/iOSApp.swift b/ios-sample/ios-sample/iOSApp.swift deleted file mode 100644 index 0648e86..0000000 --- a/ios-sample/ios-sample/iOSApp.swift +++ /dev/null @@ -1,10 +0,0 @@ -import SwiftUI - -@main -struct iOSApp: App { - var body: some Scene { - WindowGroup { - ContentView() - } - } -} \ No newline at end of file