diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1b55692 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +project_generator.py linguist-vendored \ No newline at end of file diff --git a/.gitignore b/.gitignore index d21abc8..0ec1274 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,11 @@ .gradle -/.idea +.idea /local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store /build /captures -.DS_Store *.iml *.apk *.jobf \ No newline at end of file diff --git a/README.md b/README.md index cdef68c..ea421fb 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,21 @@ # README -This is an android application template project built with kotlin language and some useful libraries. It provides a creator script to quickly create an project from template. +[![Apache 2.0 License](https://img.shields.io/badge/license-Apache%202.0-blue.svg?style=flat)](http://www.apache.org/licenses/LICENSE-2.0.html) -## Creating project +This project template makes it easy to get started with [**Kotlin**](https://kotlinlang.org) in Android development. It provides a python script that can generate a new Android project using Kotlin/MVP/ReactiveX. Just paste and execute the following command at a terminal prompt (it depends on the [`requests`](http://docs.python-requests.org/en/master/user/install/) lib). -Make sure you have installed Python 3 and [requests](https://pypi.org/project/requests/) library before proceeding. And then paste the following command at a terminal, replace the `PROJECT_NAME` and `APP_PACKAGE_NAME` and execute it: - -```sh -python3 -c \ -"$(curl -fsSL https://raw.githubusercontent.com/nekocode/create-android-kotlin-app/master/create-android-kotlin-app.py)" \ -PROJECT_NAME \ -APP_PACKAGE_NAME +```bash +python -c "$(curl -fsSL https://raw.githubusercontent.com/nekocode/Kotlin-Android-Template/master/project_generator.py)" ``` -## What is included +### Project Structure + +This project demonstrates a basic [Model-View-Presenter](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter) (MVP) architecture. It uses headless Fragment to implement Presenter because the Fragment provides lifecycle callbacks and can be recreated automatically by the FragmentManager. And the project entirely separates the business logic code into a submodule so you can maintain and test them separately. + +![](art/layer.png) -This template project includes some of the latest features in Android development: +This project uses the router APIs ([UIRouter.kt](sample/src/main/java/cn/nekocode/template/screen/UIRouter.kt)) generated by [Meepo](https://github.com/nekocode/Meepo) to navigate between Activities. You can call these APIs directly in the Presenter just like [here](sample/src/main/java/cn/nekocode/template/screen/main/MainPresenter.kt#L36). And this project uses Robolectric to test the data services (/bussiness logic). Check the [GankServiceTest.kt](data/src/test/java/cn/nekocode/template/data/GankServiceTest.kt) for more detail. -- Uses kotlin completely (includes gradle build scripts) -- Uses AndroidX & Android Architecture Components (includes Navigation, ViewModel, LiveData) -- Uses some powerful generic libraries like RxKotlin, Dagger2, etc -- Includes some useful features & extensions: - - Provides convenient way to inject dependencies to Activity, Fragment & ViewModel - - Provides extension methods `autoDisposable()` for auto disposing rx streams in Activity, Fragment & ViewModel +### Contribution -For more details, you can check the source code directly. +Feel free to contribute to this project by either raising issues or handing in pull requests. diff --git a/app/build.gradle.kts b/app/build.gradle.kts deleted file mode 100644 index 6ee3f4e..0000000 --- a/app/build.gradle.kts +++ /dev/null @@ -1,96 +0,0 @@ -import org.jetbrains.kotlin.config.KotlinCompilerVersion - -plugins { - id("com.android.application") - kotlin("android") - kotlin("kapt") - id("kotlin-parcelize") - id("androidx.navigation.safeargs") - id("com.akaita.android.easylauncher") -} - -android { - compileSdkVersion(29) - defaultConfig { - minSdkVersion(21) - applicationId = "cn.nekocode.caka" - versionCode = 1 - versionName = "1.0" - - val scheme = "caka" - - buildConfigField("String", "SCHEME", "\"$scheme\"") - - addManifestPlaceholders(mapOf( - "APPLICATION_ID" to applicationId!!, - "SCHEME" to scheme - )) - } - buildTypes { - getByName("release") { - isMinifyEnabled = false - proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") - } - } - buildFeatures { - viewBinding = true - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } -} - -dependencies { - implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) - implementation(project(":backend")) - - // Kotlin - implementation(kotlin("stdlib-jdk8", KotlinCompilerVersion.VERSION)) - - // Androidx - implementation("androidx.appcompat:appcompat:1.2.0") - implementation("androidx.core:core-ktx:1.3.2") - implementation("androidx.recyclerview:recyclerview:1.2.0-beta01") - implementation("androidx.constraintlayout:constraintlayout:2.1.0-alpha2") - implementation("androidx.lifecycle:lifecycle-extensions:2.2.0") - - // Navigation - implementation("androidx.navigation:navigation-fragment-ktx:2.3.3") - implementation("androidx.navigation:navigation-ui-ktx:2.3.3") - - // ReactiveX - implementation("com.uber.autodispose2:autodispose:2.0.0") - implementation("com.uber.autodispose2:autodispose-android:2.0.0") - implementation("com.uber.autodispose2:autodispose-androidx-lifecycle:2.0.0") - - // Dependency injection - implementation("com.google.dagger:dagger:2.31.2") - kapt("com.google.dagger:dagger-compiler:2.31.2") - - // Others - implementation("com.jakewharton.timber:timber:4.7.1") - implementation("com.evernote:android-state:1.4.1") - kapt("com.evernote:android-state-processor:1.4.1") - - // For debugging - debugImplementation("com.facebook.flipper:flipper:0.23.2") - debugImplementation("com.facebook.soloader:soloader:0.10.1") - releaseImplementation("com.facebook.flipper:flipper-noop:0.23.2") - debugImplementation("com.willowtreeapps.hyperion:hyperion-core:0.9.31") - debugImplementation("com.willowtreeapps.hyperion:hyperion-attr:0.9.31") - debugImplementation("com.willowtreeapps.hyperion:hyperion-build-config:0.9.31") - debugImplementation("com.willowtreeapps.hyperion:hyperion-crash:0.9.31") - debugImplementation("com.willowtreeapps.hyperion:hyperion-disk:0.9.31") - debugImplementation("com.willowtreeapps.hyperion:hyperion-geiger-counter:0.9.31") - debugImplementation("com.willowtreeapps.hyperion:hyperion-measurement:0.9.31") - debugImplementation("com.willowtreeapps.hyperion:hyperion-phoenix:0.9.31") - debugImplementation("com.willowtreeapps.hyperion:hyperion-recorder:0.9.31") - debugImplementation("com.willowtreeapps.hyperion:hyperion-shared-preferences:0.9.31") - debugImplementation("com.willowtreeapps.hyperion:hyperion-timber:0.9.31") - releaseImplementation("com.willowtreeapps.hyperion:hyperion-core-no-op:0.9.31") -} - -tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile::class.java).all { - kotlinOptions.jvmTarget = "1.8" -} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro deleted file mode 100644 index 6e25e84..0000000 --- a/app/proguard-rules.pro +++ /dev/null @@ -1,31 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile - -# OkHttp -# JSR 305 annotations are for embedding nullability information. --dontwarn javax.annotation.** -# A resource is loaded with a relative path so the package of this class must be preserved. --keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase -# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java. --dontwarn org.codehaus.mojo.animal_sniffer.* -# OkHttp platform used only on JVM and when Conscrypt dependency is available. --dontwarn okhttp3.internal.platform.ConscryptPlatform \ No newline at end of file diff --git a/app/src/debug/java/cn/nekocode/caka/di/module/FlipperModule.kt b/app/src/debug/java/cn/nekocode/caka/di/module/FlipperModule.kt deleted file mode 100644 index 1ca3a8e..0000000 --- a/app/src/debug/java/cn/nekocode/caka/di/module/FlipperModule.kt +++ /dev/null @@ -1,67 +0,0 @@ -package cn.nekocode.caka.di.module - -import android.app.Application -import cn.nekocode.caka.di.AppScope -import com.facebook.flipper.android.AndroidFlipperClient -import com.facebook.flipper.android.utils.FlipperUtils -import com.facebook.flipper.core.FlipperClient -import com.facebook.flipper.core.FlipperPlugin -import com.facebook.flipper.core.FlipperStateUpdateListener -import com.facebook.flipper.core.StateSummary -import com.facebook.flipper.plugins.inspector.DescriptorMapping -import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin -import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor -import com.facebook.flipper.plugins.network.NetworkFlipperPlugin -import com.facebook.soloader.SoLoader -import dagger.Module -import dagger.Provides -import okhttp3.OkHttpClient - -/** - * @author nekocode (nekocode.cn@gmail.com) - */ -@Module -class FlipperModule( - private val app: Application, - private val httpClientBuilder: OkHttpClient.Builder -) { - - @Provides - @AppScope - fun provideClient(): FlipperClient { - return if (FlipperUtils.shouldEnableFlipper(app)) { - SoLoader.init(app, false) - - AndroidFlipperClient.getInstance(app).apply { - // Layout inspecting - addPlugin( - InspectorFlipperPlugin(app, DescriptorMapping.withDefaults()) - ) - - // Network inspecting - NetworkFlipperPlugin().let { - addPlugin(it) - httpClientBuilder.addNetworkInterceptor(FlipperOkhttpInterceptor(it)) - } - } - - } else { - NO_OP_CLIENT - } - } - - companion object { - private val NO_OP_CLIENT = object : FlipperClient { - override fun getState(): String? = null - override fun getStateSummary(): StateSummary? = null - override fun addPlugin(plugin: FlipperPlugin?) {} - override fun getPlugin(id: String?): T? = null - override fun getPluginByClass(cls: Class?): T? = null - override fun removePlugin(plugin: FlipperPlugin?) {} - override fun start() {} - override fun stop() {} - override fun unsubscribe() {} - override fun subscribeForUpdates(stateListener: FlipperStateUpdateListener?) {} - } - } -} \ No newline at end of file diff --git a/app/src/main/java/cn/nekocode/caka/MyApplication.kt b/app/src/main/java/cn/nekocode/caka/MyApplication.kt deleted file mode 100644 index e98922c..0000000 --- a/app/src/main/java/cn/nekocode/caka/MyApplication.kt +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2018. nekocode (nekocode.cn@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.nekocode.caka - -import android.app.Application -import androidx.lifecycle.ViewModelProvider -import cn.nekocode.caka.backend.di.module.ApiModule -import cn.nekocode.caka.di.component.AppComponent -import cn.nekocode.caka.di.component.DaggerAppComponent -import cn.nekocode.caka.di.module.AppModule -import cn.nekocode.caka.di.module.FlipperModule -import com.facebook.flipper.core.FlipperClient -import com.google.gson.GsonBuilder -import okhttp3.OkHttpClient -import timber.log.Timber -import javax.inject.Inject - -/** - * @author nekocode (nekocode.cn@gmail.com) - */ -open class MyApplication : Application() { - lateinit var component: AppComponent - @Inject - lateinit var flipperClient: FlipperClient - @Inject - lateinit var viewModelFactory: ViewModelProvider.Factory - - override fun onCreate() { - super.onCreate() - component = createComponent() - component.inject(this) - - if (BuildConfig.DEBUG) { - Timber.plant(Timber.DebugTree()) - } - flipperClient.start() - } - - protected open fun createComponent(): AppComponent { - val httpClientBuilder = OkHttpClient.Builder() - val gsonBuilder = GsonBuilder() - - return DaggerAppComponent.builder() - .appModule(AppModule(this)) - .flipperModule(FlipperModule(this, httpClientBuilder)) - .apiModule(ApiModule(httpClientBuilder, gsonBuilder)) - .build() - } -} diff --git a/app/src/main/java/cn/nekocode/caka/base/BaseActivity.kt b/app/src/main/java/cn/nekocode/caka/base/BaseActivity.kt deleted file mode 100644 index fd0a9cd..0000000 --- a/app/src/main/java/cn/nekocode/caka/base/BaseActivity.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2018. nekocode (nekocode.cn@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.nekocode.caka.base - -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentActivity -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import cn.nekocode.caka.MyApplication -import cn.nekocode.caka.di.component.buildAndInject -import com.evernote.android.state.StateSaver - -/** - * @author nekocode (nekocode.cn@gmail.com) - */ -abstract class BaseActivity : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - (application as MyApplication).component - .newActivityComponentBuilder().buildAndInject(this) - super.onCreate(savedInstanceState) - StateSaver.restoreInstanceState(this, savedInstanceState) - } - - override fun onSaveInstanceState(outState: Bundle) { - super.onSaveInstanceState(outState) - StateSaver.saveInstanceState(this, outState) - } - - fun getViewModel(key: String, modelClass: Class) = - getViewModelProvider(this).get(key, modelClass) - - fun getViewModel(modelClass: Class) = - getViewModelProvider(this).get(modelClass) - - fun getViewModelProvider(fragment: Fragment) = - ViewModelProvider(fragment, (application as MyApplication).viewModelFactory) - - fun getViewModelProvider(activity: FragmentActivity) = - ViewModelProvider(activity, (application as MyApplication).viewModelFactory) -} diff --git a/app/src/main/java/cn/nekocode/caka/base/BaseFragment.kt b/app/src/main/java/cn/nekocode/caka/base/BaseFragment.kt deleted file mode 100644 index cfbf5e7..0000000 --- a/app/src/main/java/cn/nekocode/caka/base/BaseFragment.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2019. nekocode (nekocode.cn@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.nekocode.caka.base - -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentActivity -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import cn.nekocode.caka.MyApplication -import cn.nekocode.caka.di.component.buildAndInject -import com.evernote.android.state.StateSaver - -/** - * @author nekocode (nekocode.cn@gmail.com) - */ -abstract class BaseFragment : Fragment { - constructor() : super() - constructor(contentLayoutId: Int) : super(contentLayoutId) - - override fun onCreate(savedInstanceState: Bundle?) { - application?.component?.newFragmentComponentBuilder()?.buildAndInject(this) - super.onCreate(savedInstanceState) - StateSaver.restoreInstanceState(this, savedInstanceState) - } - - override fun onSaveInstanceState(outState: Bundle) { - super.onSaveInstanceState(outState) - StateSaver.saveInstanceState(this, outState) - } - - val application: MyApplication? - get() = context?.applicationContext as MyApplication? - - fun setTitle(title: String) { - (activity as AppCompatActivity?)?.supportActionBar?.title = title - } - - fun getViewModel(key: String, modelClass: Class) = - getViewModelProvider(this).get(key, modelClass) - - fun getViewModel(modelClass: Class) = - getViewModelProvider(this).get(modelClass) - - fun getViewModelProvider(fragment: Fragment) = - ViewModelProvider(fragment, application!!.viewModelFactory) - - fun getViewModelProvider(activity: FragmentActivity) = - ViewModelProvider(activity, application!!.viewModelFactory) -} \ No newline at end of file diff --git a/app/src/main/java/cn/nekocode/caka/base/BaseViewModel.kt b/app/src/main/java/cn/nekocode/caka/base/BaseViewModel.kt deleted file mode 100644 index abe2302..0000000 --- a/app/src/main/java/cn/nekocode/caka/base/BaseViewModel.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2019. nekocode (nekocode.cn@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.nekocode.caka.base - -import androidx.lifecycle.ViewModel -import autodispose2.lifecycle.CorrespondingEventsFunction -import autodispose2.lifecycle.LifecycleEndedException -import autodispose2.lifecycle.LifecycleScopeProvider -import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.subjects.BehaviorSubject - -/** - * Copied and modified from: https://github.com/uber/AutoDispose - * - * @author nekocode (nekocode.cn@gmail.com) - */ -abstract class BaseViewModel : ViewModel(), LifecycleScopeProvider { - private val lifecycleEventsDelegate = - lazy { BehaviorSubject.createDefault(ViewModelEvent.CREATED) } - private val lifecycleEvents by lifecycleEventsDelegate - - enum class ViewModelEvent { - CREATED, CLEARED - } - - override fun lifecycle(): Observable { - return lifecycleEvents.hide() - } - - override fun correspondingEvents(): CorrespondingEventsFunction? { - return CORRESPONDING_EVENTS - } - - override fun peekLifecycle(): ViewModelEvent? { - return lifecycleEvents.value - } - - override fun onCleared() { - if (lifecycleEventsDelegate.isInitialized()) { - lifecycleEvents.onNext(ViewModelEvent.CLEARED) - } - - super.onCleared() - } - - companion object { - private val CORRESPONDING_EVENTS = CorrespondingEventsFunction { event -> - when (event) { - ViewModelEvent.CREATED -> ViewModelEvent.CLEARED - else -> throw LifecycleEndedException( - "Cannot bind to ViewModel lifecycle after onCleared." - ) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/cn/nekocode/caka/di/ActivityScope.kt b/app/src/main/java/cn/nekocode/caka/di/ActivityScope.kt deleted file mode 100644 index 8990afa..0000000 --- a/app/src/main/java/cn/nekocode/caka/di/ActivityScope.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2019. nekocode (nekocode.cn@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.nekocode.caka.di - -import javax.inject.Scope - -/** - * @author nekocode (nekocode.cn@gmail.com) - */ -@Scope -@Retention(AnnotationRetention.RUNTIME) -annotation class ActivityScope \ No newline at end of file diff --git a/app/src/main/java/cn/nekocode/caka/di/AppScope.kt b/app/src/main/java/cn/nekocode/caka/di/AppScope.kt deleted file mode 100644 index 29f1869..0000000 --- a/app/src/main/java/cn/nekocode/caka/di/AppScope.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2019. nekocode (nekocode.cn@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.nekocode.caka.di - -import javax.inject.Scope - -/** - * @author nekocode (nekocode.cn@gmail.com) - */ -@Scope -@Retention(AnnotationRetention.RUNTIME) -annotation class AppScope \ No newline at end of file diff --git a/app/src/main/java/cn/nekocode/caka/di/FragmentScope.kt b/app/src/main/java/cn/nekocode/caka/di/FragmentScope.kt deleted file mode 100644 index 01cebd4..0000000 --- a/app/src/main/java/cn/nekocode/caka/di/FragmentScope.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2019. nekocode (nekocode.cn@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.nekocode.caka.di - -import javax.inject.Scope - -/** - * @author nekocode (nekocode.cn@gmail.com) - */ -@Scope -@Retention(AnnotationRetention.RUNTIME) -annotation class FragmentScope \ No newline at end of file diff --git a/app/src/main/java/cn/nekocode/caka/di/ViewModelDaggerHelper.kt b/app/src/main/java/cn/nekocode/caka/di/ViewModelDaggerHelper.kt deleted file mode 100644 index 72f2506..0000000 --- a/app/src/main/java/cn/nekocode/caka/di/ViewModelDaggerHelper.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2019. nekocode (nekocode.cn@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.nekocode.caka.di - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import dagger.MapKey -import javax.inject.Inject -import javax.inject.Provider -import javax.inject.Singleton -import kotlin.reflect.KClass - -/** - * Copied and modified from: https://www.jianshu.com/p/d3c43b9dd6c6 - * - * @author nekocode (nekocode.cn@gmail.com) - */ - -@Singleton -class DaggerViewModelFactory @Inject constructor( - private val providerMap: Map, @JvmSuppressWildcards Provider> -) : ViewModelProvider.Factory { - - override fun create(modelClass: Class): T { - val creator = providerMap[modelClass] ?: providerMap.entries.firstOrNull { - modelClass.isAssignableFrom(it.key) - }?.value ?: throw IllegalArgumentException("Unknown model class $modelClass") - try { - @Suppress("UNCHECKED_CAST") - return creator.get() as T - } catch (e: Exception) { - throw RuntimeException(e) - } - } -} - -@MustBeDocumented -@Target( - AnnotationTarget.FUNCTION, - AnnotationTarget.PROPERTY_GETTER, - AnnotationTarget.PROPERTY_SETTER -) -@Retention(AnnotationRetention.RUNTIME) -@MapKey -annotation class ViewModelKey(val value: KClass) diff --git a/app/src/main/java/cn/nekocode/caka/di/component/ActivityComponent.kt b/app/src/main/java/cn/nekocode/caka/di/component/ActivityComponent.kt deleted file mode 100644 index a980fff..0000000 --- a/app/src/main/java/cn/nekocode/caka/di/component/ActivityComponent.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2019. nekocode (nekocode.cn@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.nekocode.caka.di.component - -import android.app.Activity -import cn.nekocode.caka.di.ActivityScope -import cn.nekocode.caka.di.module.ActivityModule -import cn.nekocode.caka.ui.MainActivity -import dagger.Subcomponent - -/** - * @author nekocode (nekocode.cn@gmail.com) - */ -@ActivityScope -@Subcomponent( - modules = [ - ActivityModule::class - ] -) -interface ActivityComponent { - - @Subcomponent.Builder - interface Builder { - fun activityModule(module: ActivityModule): Builder - fun build(): ActivityComponent - } - - fun inject(activity: MainActivity) -} - -fun ActivityComponent.Builder.buildAndInject(activity: Activity) { - val component = activityModule(ActivityModule(activity)).build() - - when (activity) { - is MainActivity -> component.inject(activity) - } -} \ No newline at end of file diff --git a/app/src/main/java/cn/nekocode/caka/di/component/AppComponent.kt b/app/src/main/java/cn/nekocode/caka/di/component/AppComponent.kt deleted file mode 100644 index 6ed13d5..0000000 --- a/app/src/main/java/cn/nekocode/caka/di/component/AppComponent.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2019. nekocode (nekocode.cn@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.nekocode.caka.di.component - -import cn.nekocode.caka.MyApplication -import cn.nekocode.caka.backend.di.module.ApiModule -import cn.nekocode.caka.di.AppScope -import cn.nekocode.caka.di.module.AppModule -import cn.nekocode.caka.di.module.FlipperModule -import cn.nekocode.caka.di.module.ViewModelModule -import dagger.Component -import javax.inject.Singleton - -/** - * @author nekocode (nekocode.cn@gmail.com) - */ -@AppScope -@Singleton -@Component( - modules = [ - AppModule::class, - FlipperModule::class, - ApiModule::class, - ViewModelModule::class - ] -) -interface AppComponent { - - fun inject(app: MyApplication) - - fun newActivityComponentBuilder(): ActivityComponent.Builder - - fun newFragmentComponentBuilder(): FragmentComponent.Builder -} \ No newline at end of file diff --git a/app/src/main/java/cn/nekocode/caka/di/component/FragmentComponent.kt b/app/src/main/java/cn/nekocode/caka/di/component/FragmentComponent.kt deleted file mode 100644 index ced6074..0000000 --- a/app/src/main/java/cn/nekocode/caka/di/component/FragmentComponent.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2019. nekocode (nekocode.cn@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.nekocode.caka.di.component - -import androidx.fragment.app.Fragment -import cn.nekocode.caka.di.FragmentScope -import cn.nekocode.caka.di.module.FragmentModule -import cn.nekocode.caka.ui.home.HomeFragment -import cn.nekocode.caka.ui.repo.RepoFragment -import dagger.Subcomponent - -/** - * @author nekocode (nekocode.cn@gmail.com) - */ -@FragmentScope -@Subcomponent( - modules = [ - FragmentModule::class - ] -) -interface FragmentComponent { - - @Subcomponent.Builder - interface Builder { - fun fragmentModule(module: FragmentModule): Builder - fun build(): FragmentComponent - } - - fun inject(fragment: HomeFragment) - fun inject(fragment: RepoFragment) -} - -fun FragmentComponent.Builder.buildAndInject(fragment: Fragment) { - val component = fragmentModule(FragmentModule(fragment)).build() - - when (fragment) { - is HomeFragment -> component.inject(fragment) - is RepoFragment -> component.inject(fragment) - } -} \ No newline at end of file diff --git a/app/src/main/java/cn/nekocode/caka/di/module/ActivityModule.kt b/app/src/main/java/cn/nekocode/caka/di/module/ActivityModule.kt deleted file mode 100644 index 00a85fe..0000000 --- a/app/src/main/java/cn/nekocode/caka/di/module/ActivityModule.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2019. nekocode (nekocode.cn@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.nekocode.caka.di.module - -import android.app.Activity -import cn.nekocode.caka.di.ActivityScope -import dagger.Module -import dagger.Provides - -/** - * @author nekocode (nekocode.cn@gmail.com) - */ -@Module -class ActivityModule(private val activity: Activity) { - - @Provides - @ActivityScope - fun provideActivity(): Activity = activity -} \ No newline at end of file diff --git a/app/src/main/java/cn/nekocode/caka/di/module/AppModule.kt b/app/src/main/java/cn/nekocode/caka/di/module/AppModule.kt deleted file mode 100644 index 09136f2..0000000 --- a/app/src/main/java/cn/nekocode/caka/di/module/AppModule.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2019. nekocode (nekocode.cn@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.nekocode.caka.di.module - -import android.app.Application -import cn.nekocode.caka.di.AppScope -import cn.nekocode.caka.di.component.ActivityComponent -import cn.nekocode.caka.di.component.FragmentComponent -import dagger.Module -import dagger.Provides - -/** - * @author nekocode (nekocode.cn@gmail.com) - */ -@Module( - subcomponents = [ - ActivityComponent::class, - FragmentComponent::class - ] -) -class AppModule(private val app: Application) { - - @Provides - @AppScope - fun provideApp(): Application = app -} \ No newline at end of file diff --git a/app/src/main/java/cn/nekocode/caka/di/module/FragmentModule.kt b/app/src/main/java/cn/nekocode/caka/di/module/FragmentModule.kt deleted file mode 100644 index f79b272..0000000 --- a/app/src/main/java/cn/nekocode/caka/di/module/FragmentModule.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2019. nekocode (nekocode.cn@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.nekocode.caka.di.module - -import androidx.fragment.app.Fragment -import cn.nekocode.caka.di.FragmentScope -import dagger.Module -import dagger.Provides - -/** - * @author nekocode (nekocode.cn@gmail.com) - */ -@Module -class FragmentModule(private val fragment: Fragment) { - - @Provides - @FragmentScope - fun provideFragment(): Fragment = fragment -} \ No newline at end of file diff --git a/app/src/main/java/cn/nekocode/caka/di/module/ViewModelModule.kt b/app/src/main/java/cn/nekocode/caka/di/module/ViewModelModule.kt deleted file mode 100644 index fbc5c1c..0000000 --- a/app/src/main/java/cn/nekocode/caka/di/module/ViewModelModule.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2019. nekocode (nekocode.cn@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.nekocode.caka.di.module - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import cn.nekocode.caka.di.DaggerViewModelFactory -import cn.nekocode.caka.di.ViewModelKey -import cn.nekocode.caka.ui.home.HomeViewModel -import dagger.Binds -import dagger.Module -import dagger.multibindings.IntoMap - -/** - * @author nekocode (nekocode.cn@gmail.com) - */ -@Module -abstract class ViewModelModule { - - @Binds - abstract fun bindViewModelFactory(factory: DaggerViewModelFactory): ViewModelProvider.Factory - - @Binds - @IntoMap - @ViewModelKey(HomeViewModel::class) - abstract fun bindHomeViewModel(viewModel: HomeViewModel): ViewModel -} \ No newline at end of file diff --git a/app/src/main/java/cn/nekocode/caka/ui/MainActivity.kt b/app/src/main/java/cn/nekocode/caka/ui/MainActivity.kt deleted file mode 100644 index 4a38239..0000000 --- a/app/src/main/java/cn/nekocode/caka/ui/MainActivity.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2018. nekocode (nekocode.cn@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.nekocode.caka.ui - -import android.os.Bundle -import androidx.navigation.findNavController -import androidx.navigation.ui.setupActionBarWithNavController -import cn.nekocode.caka.R -import cn.nekocode.caka.base.BaseActivity - -/** - * @author nekocode (nekocode.cn@gmail.com) - */ -class MainActivity : BaseActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - - val navController = findNavController(R.id.frag_nav_host) - setupActionBarWithNavController(navController) - } - - override fun onSupportNavigateUp() = - findNavController(R.id.frag_nav_host).navigateUp() -} diff --git a/app/src/main/java/cn/nekocode/caka/ui/home/HomeFragment.kt b/app/src/main/java/cn/nekocode/caka/ui/home/HomeFragment.kt deleted file mode 100644 index 31e69a5..0000000 --- a/app/src/main/java/cn/nekocode/caka/ui/home/HomeFragment.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2019. nekocode (nekocode.cn@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.nekocode.caka.ui.home - -import android.os.Bundle -import android.view.View -import androidx.lifecycle.Observer -import androidx.navigation.fragment.findNavController -import cn.nekocode.caka.R -import cn.nekocode.caka.base.BaseFragment -import cn.nekocode.caka.databinding.FragmentHomeBinding - -/** - * @author nekocode (nekocode.cn@gmail.com) - */ -class HomeFragment : BaseFragment(R.layout.fragment_home) { - private val vm by lazy { - getViewModel(HomeViewModel::class.java) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - val binding = FragmentHomeBinding.bind(view) - - val adapter = ReposAdapter { repo -> - findNavController().navigate(HomeFragmentDirections.actionHomeToRepo(repo)) - } - binding.recyclerView.adapter = adapter - binding.recyclerView.visibility = View.INVISIBLE - binding.progressBar.visibility = View.VISIBLE - - vm.reposLiveData.observe(viewLifecycleOwner, Observer { repos -> - adapter.submitList(repos) - binding.recyclerView.visibility = View.VISIBLE - binding.progressBar.visibility = View.INVISIBLE - }) - } -} diff --git a/app/src/main/java/cn/nekocode/caka/ui/home/HomeViewModel.kt b/app/src/main/java/cn/nekocode/caka/ui/home/HomeViewModel.kt deleted file mode 100644 index 05b432b..0000000 --- a/app/src/main/java/cn/nekocode/caka/ui/home/HomeViewModel.kt +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2021. nekocode (nekocode.cn@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.nekocode.caka.ui.home - -import android.app.Application -import android.widget.Toast -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import autodispose2.autoDispose -import cn.nekocode.caka.backend.api.RepoApi -import cn.nekocode.caka.backend.model.Repository -import cn.nekocode.caka.base.BaseViewModel -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers -import io.reactivex.rxjava3.schedulers.Schedulers -import javax.inject.Inject - -/** - * @author nekocode (nekocode.cn@gmail.com) - */ -class HomeViewModel @Inject constructor( - private val app: Application, - private val repoApi: RepoApi -) : BaseViewModel() { - val reposLiveData: LiveData> = MutableLiveData() - - init { - fetchRepos() - } - - private fun fetchRepos() { - reposLiveData as MutableLiveData - repoApi.listRepos("nekocode") - .retry() - .map { repos -> - repos.sortedByDescending { it.stargazersCount } - } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .autoDispose(this) - .subscribe({ repos -> - reposLiveData.postValue(repos) - }, { err -> - Toast.makeText(app, err.message, Toast.LENGTH_SHORT).show() - }) - } -} \ No newline at end of file diff --git a/app/src/main/java/cn/nekocode/caka/ui/home/ReposAdapter.kt b/app/src/main/java/cn/nekocode/caka/ui/home/ReposAdapter.kt deleted file mode 100644 index fafa38e..0000000 --- a/app/src/main/java/cn/nekocode/caka/ui/home/ReposAdapter.kt +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2021. nekocode (nekocode.cn@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.nekocode.caka.ui.home - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.ListAdapter -import androidx.recyclerview.widget.RecyclerView -import cn.nekocode.caka.R -import cn.nekocode.caka.backend.model.Repository -import cn.nekocode.caka.databinding.ItemRepoBinding - -/** - * @author nekocode (nekocode.cn@gmail.com) - */ -class ReposAdapter( - private val onClick: (Repository) -> Unit -) : ListAdapter(RepositoryDiffCallback) { - - inner class ViewHolder( - private val binding: ItemRepoBinding - ) : RecyclerView.ViewHolder(binding.root) { - private var currentRepo: Repository? = null - - init { - itemView.setOnClickListener { - currentRepo?.let { - onClick(it) - } - } - } - - fun bind(repo: Repository) { - currentRepo = repo - - val context = itemView.context - binding.tvTitle.text = repo.name - binding.tvBody.text = repo.description - binding.tvBottom.text = context.getString( - R.string.repo_bottom_text, - repo.stargazersCount, - repo.forksCount - ) - } - } - - override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder { - val view = LayoutInflater.from(viewGroup.context) - .inflate(R.layout.item_repo, viewGroup, false) - return ViewHolder(ItemRepoBinding.bind(view)) - } - - override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) { - val repo = getItem(position) - viewHolder.bind(repo) - } -} - -object RepositoryDiffCallback : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: Repository, newItem: Repository): Boolean { - return oldItem == newItem - } - - override fun areContentsTheSame(oldItem: Repository, newItem: Repository): Boolean { - return oldItem.id == newItem.id - } -} \ No newline at end of file diff --git a/app/src/main/java/cn/nekocode/caka/ui/repo/RepoFragment.kt b/app/src/main/java/cn/nekocode/caka/ui/repo/RepoFragment.kt deleted file mode 100644 index 6c99b38..0000000 --- a/app/src/main/java/cn/nekocode/caka/ui/repo/RepoFragment.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2019. nekocode (nekocode.cn@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.nekocode.caka.ui.repo - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.navigation.fragment.navArgs -import cn.nekocode.caka.R -import cn.nekocode.caka.base.BaseFragment -import cn.nekocode.caka.databinding.FragmentRepoBinding - -/** - * @author nekocode (nekocode.cn@gmail.com) - */ -class RepoFragment : BaseFragment() { - private val args: RepoFragmentArgs by navArgs() - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - setTitle(args.repo.name) - return inflater.inflate(R.layout.fragment_repo, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - val binding = FragmentRepoBinding.bind(view) - - binding.webView.loadUrl(args.repo.url) - } -} diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml deleted file mode 100644 index 9486baf..0000000 --- a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index dac412b..0000000 --- a/app/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,205 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index fe9e0b4..0000000 --- a/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml deleted file mode 100644 index 68d5872..0000000 --- a/app/src/main/res/layout/fragment_home.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_repo.xml b/app/src/main/res/layout/fragment_repo.xml deleted file mode 100644 index 71e3a10..0000000 --- a/app/src/main/res/layout/fragment_repo.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/item_repo.xml b/app/src/main/res/layout/item_repo.xml deleted file mode 100644 index d937810..0000000 --- a/app/src/main/res/layout/item_repo.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml deleted file mode 100644 index bbd3e02..0000000 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml deleted file mode 100644 index bbd3e02..0000000 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index a2f5908..0000000 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index 1b52399..0000000 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index ff10afd..0000000 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index 115a4c7..0000000 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index dcd3cd8..0000000 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index 459ca60..0000000 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 8ca12fe..0000000 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index 8e19b41..0000000 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index b824ebd..0000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index 4c19a13..0000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/main/res/navigation/nav_main.xml b/app/src/main/res/navigation/nav_main.xml deleted file mode 100644 index 1eb0e9c..0000000 --- a/app/src/main/res/navigation/nav_main.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml deleted file mode 100644 index a2de4fc..0000000 --- a/app/src/main/res/values/colors.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - #3F51B5 - #303F9F - #FF4081 - - #ffffff - #f5f5f5 - #e4e4e4 - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml deleted file mode 100644 index 51b1254..0000000 --- a/app/src/main/res/values/strings.xml +++ /dev/null @@ -1,4 +0,0 @@ - - create-android-kotlin-app - stargazers: %1$d forks: %2$d - diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml deleted file mode 100644 index 16c1751..0000000 --- a/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - diff --git a/app/src/release/java/cn/nekocode/caka/di/module/FlipperModule.kt b/app/src/release/java/cn/nekocode/caka/di/module/FlipperModule.kt deleted file mode 100644 index 0f1b0c1..0000000 --- a/app/src/release/java/cn/nekocode/caka/di/module/FlipperModule.kt +++ /dev/null @@ -1,23 +0,0 @@ -package cn.nekocode.caka.di.module - -import android.app.Application -import cn.nekocode.caka.di.AppScope -import com.facebook.flipper.android.AndroidFlipperClient -import com.facebook.flipper.core.FlipperClient -import dagger.Module -import dagger.Provides -import okhttp3.OkHttpClient - -/** - * @author nekocode (nekocode.cn@gmail.com) - */ -@Module -class FlipperModule( - private val app: Application, - private val httpClientBuilder: OkHttpClient.Builder -) { - - @Provides - @AppScope - fun provideClient(): FlipperClient = AndroidFlipperClient.getInstance(app) -} \ No newline at end of file diff --git a/art/layer.png b/art/layer.png new file mode 100644 index 0000000..1570b07 Binary files /dev/null and b/art/layer.png differ diff --git a/backend/build.gradle.kts b/backend/build.gradle.kts deleted file mode 100644 index e8aae8f..0000000 --- a/backend/build.gradle.kts +++ /dev/null @@ -1,52 +0,0 @@ -import org.jetbrains.kotlin.config.KotlinCompilerVersion - -plugins { - id("com.android.library") - kotlin("android") - kotlin("kapt") - id("kotlin-parcelize") -} - -android { - compileSdkVersion(29) - defaultConfig { - minSdkVersion(21) - consumerProguardFiles("proguard-rules.pro") - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } -} - -dependencies { - implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) - testImplementation("junit:junit:4.13.1") - - // Kotlin - implementation(kotlin("stdlib-jdk8", KotlinCompilerVersion.VERSION)) - - // Network - api("com.squareup.okhttp3:okhttp:4.9.1") - api("com.google.code.gson:gson:2.8.6") - api("com.squareup.retrofit2:retrofit:2.9.0") - implementation("com.squareup.retrofit2:converter-gson:2.9.0") - implementation("com.squareup.retrofit2:adapter-rxjava3:2.9.0") - testImplementation("com.squareup.okhttp3:logging-interceptor:4.9.1") - - // ReactiveX - api("io.reactivex.rxjava3:rxkotlin:3.0.1") - api("io.reactivex.rxjava3:rxandroid:3.0.0") - - // Dependency injection - implementation("com.google.dagger:dagger:2.31.2") - kapt("com.google.dagger:dagger-compiler:2.31.2") - kaptTest("com.google.dagger:dagger-compiler:2.31.2") - - // Testing - testImplementation(kotlin("test-junit", KotlinCompilerVersion.VERSION)) -} - -tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile::class.java).all { - kotlinOptions.jvmTarget = "1.8" -} diff --git a/backend/proguard-rules.pro b/backend/proguard-rules.pro deleted file mode 100644 index f1b4245..0000000 --- a/backend/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile diff --git a/backend/src/main/AndroidManifest.xml b/backend/src/main/AndroidManifest.xml deleted file mode 100644 index 02cd433..0000000 --- a/backend/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - diff --git a/backend/src/main/java/cn/nekocode/caka/backend/api/RepoApi.kt b/backend/src/main/java/cn/nekocode/caka/backend/api/RepoApi.kt deleted file mode 100644 index e89f646..0000000 --- a/backend/src/main/java/cn/nekocode/caka/backend/api/RepoApi.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2018. nekocode (nekocode.cn@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.nekocode.caka.backend.api - -import cn.nekocode.caka.backend.model.Repository -import io.reactivex.rxjava3.core.Observable -import retrofit2.http.GET -import retrofit2.http.Path -import retrofit2.http.Query - -/** - * @author nekocode (nekocode.cn@gmail.com) - */ -interface RepoApi { - - @GET("/users/{username}/repos") - fun listRepos( - @Path("username") username: String, - @Query("per_page") perPage: Int = 100 - ): Observable> -} diff --git a/backend/src/main/java/cn/nekocode/caka/backend/di/module/ApiModule.kt b/backend/src/main/java/cn/nekocode/caka/backend/di/module/ApiModule.kt deleted file mode 100644 index c4c7b7c..0000000 --- a/backend/src/main/java/cn/nekocode/caka/backend/di/module/ApiModule.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2019. nekocode (nekocode.cn@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.nekocode.caka.backend.di.module - -import cn.nekocode.caka.backend.api.RepoApi -import com.google.gson.Gson -import com.google.gson.GsonBuilder -import dagger.Module -import dagger.Provides -import okhttp3.OkHttpClient -import retrofit2.Retrofit -import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory -import retrofit2.converter.gson.GsonConverterFactory -import javax.inject.Singleton - -/** - * @author nekocode (nekocode.cn@gmail.com) - */ -@Module -class ApiModule( - private val httpClientBuilder: OkHttpClient.Builder, - private val gsonBuilder: GsonBuilder, - private val env: Env = Env.PRODUCT -) { - - enum class Env(val baseUrl: String) { - PRODUCT("https://api.github.com"), - } - - @Provides - @Singleton - fun provideHttpClient(): OkHttpClient = httpClientBuilder - .addInterceptor { chain -> - val oldReq = chain.request() - val newReqBuilder = oldReq.newBuilder() - - // Add headers - newReqBuilder.addHeader( - "previews", - "mercy-preview" - ) - - chain.proceed(newReqBuilder.build()) - } - .build() - - @Provides - @Singleton - fun provideGson(): Gson = gsonBuilder.create() - - @Provides - @Singleton - fun provideRetrofit(httpClient: OkHttpClient, gson: Gson): Retrofit = Retrofit.Builder() - .baseUrl(env.baseUrl) - .client(httpClient) - .addConverterFactory(GsonConverterFactory.create(gson)) - .addCallAdapterFactory(RxJava3CallAdapterFactory.create()) - .build() - - @Provides - @Singleton - fun provideRepoApi(retrofit: Retrofit): RepoApi = retrofit.create(RepoApi::class.java) -} \ No newline at end of file diff --git a/backend/src/main/java/cn/nekocode/caka/backend/model/Repository.kt b/backend/src/main/java/cn/nekocode/caka/backend/model/Repository.kt deleted file mode 100644 index a90b1e3..0000000 --- a/backend/src/main/java/cn/nekocode/caka/backend/model/Repository.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2018. nekocode (nekocode.cn@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.nekocode.caka.backend.model - -import android.os.Parcelable -import com.google.gson.annotations.SerializedName -import kotlinx.parcelize.Parcelize - -/** - * @author nekocode (nekocode.cn@gmail.com) - */ -@Parcelize -data class Repository( - @SerializedName("id") val id: Int, - @SerializedName("name") val name: String, - @SerializedName("html_url") val url: String, - @SerializedName("description") val description: String, - @SerializedName("stargazers_count") val stargazersCount: Int, - @SerializedName("forks_count") val forksCount: Int -) : Parcelable diff --git a/backend/src/test/java/cn/nekocode/caka/backend/ApiTest.kt b/backend/src/test/java/cn/nekocode/caka/backend/ApiTest.kt deleted file mode 100644 index 2b6864f..0000000 --- a/backend/src/test/java/cn/nekocode/caka/backend/ApiTest.kt +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2018. nekocode (nekocode.cn@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.nekocode.caka.backend - -import cn.nekocode.caka.backend.api.RepoApi -import cn.nekocode.caka.backend.di.module.ApiModule -import com.google.gson.GsonBuilder -import okhttp3.OkHttpClient -import okhttp3.logging.HttpLoggingInterceptor -import org.junit.Ignore -import org.junit.Test -import javax.inject.Inject - -/** - * @author nekocode (nekocode.cn@gmail.com) - */ -class ApiTest { - init { - val httpClientBuilder = OkHttpClient.Builder() - .addInterceptor(HttpLoggingInterceptor { message -> - println(message) - }.apply { - level = HttpLoggingInterceptor.Level.BODY - }) - - DaggerTestComponent.builder() - .apiModule(ApiModule(httpClientBuilder, GsonBuilder())) - .build() - .inject(this) - } - - @Inject - lateinit var repoApi: RepoApi - - @Test - @Ignore("Comment this line to test this method") - fun listRepos() { - repoApi.listRepos("nekocode") - .test() - .await() - .assertNoErrors() - .assertValue { repos -> - repos.size > 0 - } - } -} diff --git a/backend/src/test/java/cn/nekocode/caka/backend/TestComponent.kt b/backend/src/test/java/cn/nekocode/caka/backend/TestComponent.kt deleted file mode 100644 index 4ff2e9f..0000000 --- a/backend/src/test/java/cn/nekocode/caka/backend/TestComponent.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2019. nekocode (nekocode.cn@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.nekocode.caka.backend - -import cn.nekocode.caka.backend.di.module.ApiModule -import dagger.Component -import javax.inject.Singleton - -/** - * @author nekocode (nekocode.cn@gmail.com) - */ -@Singleton -@Component( - modules = [ - ApiModule::class - ] -) -interface TestComponent { - fun inject(app: ApiTest) -} \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..9dccc83 --- /dev/null +++ b/build.gradle @@ -0,0 +1,22 @@ +buildscript { + repositories { + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:2.3.2' + classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' + classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4' + + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$KOTLIN_VERSION" + } +} + +allprojects { + repositories { + jcenter() + maven { + url "https://jitpack.io" + } + } +} diff --git a/build.gradle.kts b/build.gradle.kts deleted file mode 100644 index bbc4c93..0000000 --- a/build.gradle.kts +++ /dev/null @@ -1,24 +0,0 @@ -buildscript { - repositories { - google() - jcenter() - } - dependencies { - classpath("com.android.tools.build:gradle:4.1.2") - classpath(kotlin("gradle-plugin", version = "1.4.30")) - classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.3.3") - classpath("com.akaita.android:easylauncher:1.3.1") - } -} - -allprojects { - repositories { - google() - maven { url = uri("https://jitpack.io") } - jcenter() - } -} - -tasks.register("clean", Delete::class.java, Action { - delete(rootProject.buildDir) -}) diff --git a/create-android-kotlin-app.py b/create-android-kotlin-app.py deleted file mode 100755 index d444cca..0000000 --- a/create-android-kotlin-app.py +++ /dev/null @@ -1,174 +0,0 @@ -#!/usr/bin/env python -# coding:utf-8 - -import sys -import os -import io -import zipfile -import requests -import re - - -REPO_URL = 'https://github.com/nekocode/create-android-kotlin-app' -ORIGINAL_PACKAGE_NAME = 'cn.nekocode.caka' -ZIP_UNIX_SYSTEM = 3 - - -class ProjectCreator: - def __init__(self, archive_data: bytes, project_name: str, package_name: str): - self.zip_file = zipfile.ZipFile(io.BytesIO(archive_data), 'r') - self.project_name = project_name - self.package_name = package_name - self.root_dir_path = None - - def create(self): - infolist: [zipfile.ZipInfo] = self.zip_file.infolist() - self.root_dir_path = infolist[0].filename - - for info in infolist: - if info.filename.endswith('/'): - # skip directory - continue - - last_segment = info.filename.split('/')[-1] - if last_segment.endswith('.kt'): - self.kt_file(info) - - elif last_segment.endswith('.gradle.kts'): - self.gradle_file(info) - - elif last_segment.endswith('.xml'): - self.xml_file(info) - - elif last_segment.endswith('.py') or last_segment == 'README.md': - continue - - else: - self.common_file(info) - - def kt_file(self, info: zipfile.ZipInfo): - file_name = self.replace_root_path(info.filename) - file_name = file_name.replace( - ORIGINAL_PACKAGE_NAME.replace('.', '/'), - self.package_name.replace('.', '/')) - content = TextProcessor(self.zip_file.read(info.filename).decode('utf-8'))\ - .replace_all_text(ORIGINAL_PACKAGE_NAME, self.package_name)\ - .remove_unwanted_comments().commit().encode() - - self.write_to_file(file_name, content, info) - - def gradle_file(self, info: zipfile.ZipInfo): - self.replaceable_file(info) - - def xml_file(self, info: zipfile.ZipInfo): - self.replaceable_file(info) - - def replaceable_file(self, info: zipfile.ZipInfo): - file_name = self.replace_root_path(info.filename) - content = TextProcessor(self.zip_file.read(info.filename).decode('utf-8')) \ - .replace_all_text(ORIGINAL_PACKAGE_NAME, self.package_name) \ - .commit().encode() - - self.write_to_file(file_name, content, info) - - def common_file(self, info: zipfile.ZipInfo): - file_name = self.replace_root_path(info.filename) - content = self.zip_file.read(info.filename) - - self.write_to_file(file_name, content, info) - - def replace_root_path(self, file_name: str) -> str: - return file_name.replace(self.root_dir_path, self.project_name + '/') - - @staticmethod - def write_to_file(file_name: str, content: bytes, info: zipfile.ZipInfo): - os.makedirs(os.path.dirname(file_name), exist_ok=True) - open(file_name, 'wb').write(content) - if info.create_system == ZIP_UNIX_SYSTEM: - unix_attributes = info.external_attr >> 16 - if unix_attributes: - os.chmod(file_name, unix_attributes) - - -class TextProcessor: - def __init__(self, txt: str): - self.txt = txt - self.commands = [] - - def replace_all_text(self, src: str, dst: str) -> 'TextProcessor': - self.commands.append(('replace_all_text', src, dst)) - return self - - def remove_unwanted_comments(self) -> 'TextProcessor': - self.commands.append(('remove_unwanted_comments', None)) - return self - - def commit(self) -> str: - rlt = '' - for line in self.txt.splitlines(): - skip_line = False - - for cmd in self.commands: - if cmd[0] == 'replace_all_text': - line = line.replace(cmd[1], cmd[2]) - - elif cmd[0] == 'remove_unwanted_comments' and ( - line.startswith('/*') or - line.startswith(' *') or - line.startswith(' */')): - skip_line = True - - if not skip_line: - if not (len(rlt) == 0 and len(line) == 0): - rlt += line + '\n' - - return rlt - - -def fetch_latest_archive() -> bytes: - r = requests.get(REPO_URL + '/releases/latest', allow_redirects=False) - latest_tag = r.headers['location'].split('/')[-1] - archive_url = REPO_URL + '/archive/%s.zip' % latest_tag - return requests.get(archive_url).content - - -def main(): - def green(txt: str) -> str: - return '\33[32m' + txt + '\33[0m' - - def red(txt: str) -> str: - return '\33[31m' + txt + '\33[0m' - - if not (len(sys.argv) == 3): - print('Usage: python3 create-android-kotlin-app.py ' + - green(' ')) - return - - project_name = sys.argv[1] - if os.path.exists(project_name): - print(red('Error: ') + 'Can not create project. ' + - 'There is already a directory named %s in the current path.' % green(project_name)) - return - try: - os.mkdir(project_name) - except: - print(red('Error: ') + 'Can not create directory %s' % green(project_name)) - return - - package_name = sys.argv[2] - if not re.match('^[a-z][a-z0-9_]*(\.[a-z0-9_]+)+[0-9a-z_]$', package_name): - print(red('Error: ') + 'Invalid java package name %s' % green(package_name)) - return - - print('Creating a new android kotlin app in %s\n' % green("./" + project_name)) - print('Fetching the latest source code archive from %s\nThis might take a couple minutes.' % green(REPO_URL)) - archive_data = fetch_latest_archive() - - print('Unzipping template files...\n') - ProjectCreator(archive_data, project_name, package_name).create() - print('Done. Happy hacking!') - - -if __name__ == '__main__': - main() - diff --git a/app/.gitignore b/data/.gitignore similarity index 100% rename from app/.gitignore rename to data/.gitignore diff --git a/data/build.gradle b/data/build.gradle new file mode 100644 index 0000000..f9c9b4a --- /dev/null +++ b/data/build.gradle @@ -0,0 +1,41 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' + +android { + compileSdkVersion(COMPILE_SDK_VERSION.toInteger()) + buildToolsVersion(BUILD_TOOLS_VERSION) + + defaultConfig { + minSdkVersion 17 + targetSdkVersion 25 + } + + publishNonDefault true +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + testCompile 'junit:junit:4.12' + testCompile "org.robolectric:robolectric:3.3.2" + + // Android support libraries + compile "com.android.support:support-annotations:$SUPPORT_LIBS_VERSION" + + // Retrofit + def retrofitVersion = "2.3.0" + compile "com.squareup.retrofit2:retrofit:$retrofitVersion" + compile "com.squareup.retrofit2:converter-gson:$retrofitVersion" + compile "com.squareup.retrofit2:adapter-rxjava2:$retrofitVersion" + + // Storage library + compile "io.paperdb:paperdb:2.0" + + // Instance State + def paperparcelVersion = "2.0.1" + compile "nz.bradcampbell:paperparcel:$paperparcelVersion" + compile "nz.bradcampbell:paperparcel-kotlin:$paperparcelVersion" + kapt "nz.bradcampbell:paperparcel-compiler:$paperparcelVersion" + + compile "org.jetbrains.kotlin:kotlin-stdlib:$KOTLIN_VERSION" +} \ No newline at end of file diff --git a/data/proguard-rules.pro b/data/proguard-rules.pro new file mode 100644 index 0000000..9cde36e --- /dev/null +++ b/data/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in C:\Users\nekocode\AppData\Local\Android\sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} \ No newline at end of file diff --git a/data/src/main/AndroidManifest.xml b/data/src/main/AndroidManifest.xml new file mode 100644 index 0000000..8a6127e --- /dev/null +++ b/data/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + diff --git a/data/src/main/java/cn/nekocode/template/data/DO/Meizi.kt b/data/src/main/java/cn/nekocode/template/data/DO/Meizi.kt new file mode 100644 index 0000000..5715b03 --- /dev/null +++ b/data/src/main/java/cn/nekocode/template/data/DO/Meizi.kt @@ -0,0 +1,28 @@ +package cn.nekocode.template.data.DO + +import android.os.Parcel +import android.os.Parcelable +import com.google.gson.annotations.SerializedName +import paperparcel.PaperParcel + +/** + * @author nekocode (nekocode.cn@gmail.com) + */ +@PaperParcel +data class Meizi( + @SerializedName("_id") override val id: String, + val type: String, + val url: String, + val who: String +) : WithId, Parcelable { + + companion object { + @JvmField val CREATOR = PaperParcelMeizi.CREATOR + } + + override fun describeContents() = 0 + + override fun writeToParcel(dest: Parcel, flags: Int) { + PaperParcelMeizi.writeToParcel(this, dest, flags) + } +} \ No newline at end of file diff --git a/data/src/main/java/cn/nekocode/template/data/DO/Response.kt b/data/src/main/java/cn/nekocode/template/data/DO/Response.kt new file mode 100644 index 0000000..9821e92 --- /dev/null +++ b/data/src/main/java/cn/nekocode/template/data/DO/Response.kt @@ -0,0 +1,11 @@ +package cn.nekocode.template.data.DO + +import com.google.gson.annotations.SerializedName + +/** + * @author nekocode (nekocode.cn@gmail.com) + */ +internal class Response( + @SerializedName("error") val isError: Boolean, + val results: ArrayList +) \ No newline at end of file diff --git a/data/src/main/java/cn/nekocode/template/data/DO/WithId.kt b/data/src/main/java/cn/nekocode/template/data/DO/WithId.kt new file mode 100644 index 0000000..2374696 --- /dev/null +++ b/data/src/main/java/cn/nekocode/template/data/DO/WithId.kt @@ -0,0 +1,8 @@ +package cn.nekocode.template.data.DO + +/** + * @author nekocode (nekocode.cn@gmail.com) + */ +interface WithId { + val id: String +} \ No newline at end of file diff --git a/data/src/main/java/cn/nekocode/template/data/DataLayer.kt b/data/src/main/java/cn/nekocode/template/data/DataLayer.kt new file mode 100644 index 0000000..0fd19cf --- /dev/null +++ b/data/src/main/java/cn/nekocode/template/data/DataLayer.kt @@ -0,0 +1,47 @@ +package cn.nekocode.template.data + +import android.content.Context +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import io.paperdb.Paper +import okhttp3.Cache +import okhttp3.OkHttpClient +import retrofit2.Retrofit +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory +import retrofit2.converter.gson.GsonConverterFactory +import java.io.File +import java.util.concurrent.TimeUnit + +/** + * @author nekocode (nekocode.cn@gmail.com) + */ +object DataLayer { + const val HOST_GANK: String = "http://gank.io/api/data/%E7%A6%8F%E5%88%A9/" + var RETROFIT_GANK: Retrofit? = null + var CONTEXT: Context? = null + var CLIENT: OkHttpClient? = null + var GSON: Gson? = null + + + fun init(context: Context) { + DataLayer.CONTEXT = context.applicationContext + + CLIENT = OkHttpClient.Builder() + .cache(Cache(File(context.cacheDir, "okhttp"), 10 * 1024 * 1024L)) + .connectTimeout(10, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .writeTimeout(10, TimeUnit.SECONDS) + .build() + + GSON = GsonBuilder().setDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'SSS'Z'").create() + + RETROFIT_GANK = Retrofit.Builder() + .baseUrl(HOST_GANK) + .addConverterFactory(GsonConverterFactory.create(GSON)) + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .client(CLIENT) + .build() + + Paper.init(context) + } +} \ No newline at end of file diff --git a/data/src/main/java/cn/nekocode/template/data/api/GankApi.kt b/data/src/main/java/cn/nekocode/template/data/api/GankApi.kt new file mode 100644 index 0000000..5f1c886 --- /dev/null +++ b/data/src/main/java/cn/nekocode/template/data/api/GankApi.kt @@ -0,0 +1,21 @@ +package cn.nekocode.template.data.api + +import cn.nekocode.template.data.DO.Meizi +import cn.nekocode.template.data.DO.Response +import cn.nekocode.template.data.DataLayer +import io.reactivex.Observable +import retrofit2.http.GET +import retrofit2.http.Path + +/** + * @author nekocode (nekocode.cn@gmail.com) + */ +internal interface GankApi { + + companion object { + val IMPL: GankApi = DataLayer.RETROFIT_GANK!!.create(GankApi::class.java) + } + + @GET("{count}/{pageNum}") + fun getMeizis(@Path("count") count: Int, @Path("pageNum") pageNum: Int): Observable> +} \ No newline at end of file diff --git a/data/src/main/java/cn/nekocode/template/data/exception/GankServiceException.kt b/data/src/main/java/cn/nekocode/template/data/exception/GankServiceException.kt new file mode 100644 index 0000000..26cc6df --- /dev/null +++ b/data/src/main/java/cn/nekocode/template/data/exception/GankServiceException.kt @@ -0,0 +1,6 @@ +package cn.nekocode.template.data.exception + +/** + * @author nekocode (nekocode.cn@gmail.com) + */ +class GankServiceException(val msg: String?) : Exception() \ No newline at end of file diff --git a/data/src/main/java/cn/nekocode/template/data/service/GankService.kt b/data/src/main/java/cn/nekocode/template/data/service/GankService.kt new file mode 100644 index 0000000..4a2d47b --- /dev/null +++ b/data/src/main/java/cn/nekocode/template/data/service/GankService.kt @@ -0,0 +1,28 @@ +package cn.nekocode.template.data.service + +import cn.nekocode.template.data.DO.Meizi +import cn.nekocode.template.data.exception.GankServiceException +import cn.nekocode.template.data.api.GankApi +import io.paperdb.Paper +import io.reactivex.Observable +import io.reactivex.schedulers.Schedulers + +/** + * @author nekocode (nekocode.cn@gmail.com) + */ +object GankService { + + fun getMeizis(count: Int, pageNum: Int): Observable> = + GankApi.IMPL.getMeizis(count, pageNum) + .subscribeOn(Schedulers.io()) + .map { + Paper.book().write("meizis-$pageNum", it.results) + it.results + } + .onErrorResumeNext { err: Throwable -> + val list: ArrayList = Paper.book().read("meizis-$pageNum") + ?: throw GankServiceException(err.message) + Observable.just(list) + } + +} \ No newline at end of file diff --git a/data/src/test/java/cn/nekocode/template/data/GankServiceTest.kt b/data/src/test/java/cn/nekocode/template/data/GankServiceTest.kt new file mode 100644 index 0000000..77484cb --- /dev/null +++ b/data/src/test/java/cn/nekocode/template/data/GankServiceTest.kt @@ -0,0 +1,30 @@ +package cn.nekocode.template.data + +import cn.nekocode.template.data.service.GankService +import org.junit.Assert +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment +import org.robolectric.annotation.Config + +/** + * @author nekocode (nekocode.cn@gmail.com) + */ +@RunWith(RobolectricTestRunner::class) +@Config(constants = BuildConfig::class) +class GankServiceTest { + + @Before + fun setUp() { + DataLayer.init(RuntimeEnvironment.application) + } + + @Test + fun testGetMeizis() { + val meizis = GankService.getMeizis(10, 1).blockingFirst() + print(meizis) + Assert.assertTrue(meizis.isNotEmpty()) + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index ee0c96e..89912b9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,6 @@ -# Enables official code style in IDEA https://kotlinlang.org/docs/reference/coding-conventions.html -kotlin.code.style=official +kotlin.incremental=true -android.enableJetifier=true -android.useAndroidX=true +COMPILE_SDK_VERSION=25 +BUILD_TOOLS_VERSION=25.0.2 +KOTLIN_VERSION=1.1.2-3 +SUPPORT_LIBS_VERSION=25.3.1 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7a3265e..51288f9 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 19a6a06..c39139b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Feb 03 17:22:41 CST 2021 +#Sat Mar 11 04:25:55 CST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip diff --git a/gradlew b/gradlew index cccdd3d..4453cce 100755 --- a/gradlew +++ b/gradlew @@ -33,11 +33,11 @@ DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn () { +warn ( ) { echo "$*" } -die () { +die ( ) { echo echo "$*" echo @@ -155,7 +155,7 @@ if $cygwin ; then fi # Escape application args -save () { +save ( ) { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } diff --git a/project_generator.py b/project_generator.py new file mode 100755 index 0000000..2da19a4 --- /dev/null +++ b/project_generator.py @@ -0,0 +1,275 @@ +#!/usr/bin/env python +# coding:utf-8 + +import os +import shutil +import zipfile + +try: + import requests + +except ImportError: + requests = None + print 'Project Generator depends on the "requests" lib.' + + +def download_lastest_src(): + print 'Fetching the lastest version from github...' + r = requests.get('https://github.com/nekocode/Kotlin-Android-Template/releases/latest', allow_redirects=False) + lastest_tag = r.headers['Location'].split('/')[-1] + zipfile_name = 'src-%s.zip' % lastest_tag + + if os.path.exists(zipfile_name): + print 'Already downloaded [%s].' % zipfile_name + return zipfile_name, lastest_tag + + print 'Downloading the lastest release [%s]...' % zipfile_name + url = 'https://github.com/nekocode/Kotlin-Android-Template/archive/%s.zip' % lastest_tag + r = requests.get(url) + with open(zipfile_name, 'wb') as data: + data.write(r.content) + + print 'Download finished.' + return zipfile_name, lastest_tag + + +def unzip_src_package(zipfile_name): + print 'Unziping [%s]...' % zipfile_name + + zfile = zipfile.ZipFile(zipfile_name, 'r') + names = zfile.namelist() + root_dir = names[0] + + if os.path.exists(root_dir): + print 'Already unzipped.' + return root_dir + + for filename in names: + path = os.path.join('./', filename) + + if path.endswith('/'): + if not os.path.exists(os.path.dirname(path)): + os.mkdir(os.path.dirname(path)) + + else: + file(path, 'wb').write(zfile.read(filename)) + + print 'Unzip finished.' + return root_dir + + +class TextProcesser: + def __init__(self, file_path): + self.file_path = file_path + self.commands = [] + + def rm_line_has_text(self, text): + self.commands.append(('rm_line', text)) + return self + + def replace_all_text(self, src, dst): + self.commands.append(('replace', src, dst)) + return self + + def replace_header(self, src, dst): + self.commands.append(('replace_header', src, dst)) + return self + + def remove_comment(self): + self.commands.append(('rm_comment', None)) + return self + + def recreate(self, text): + self.commands = [] + self.commands.append(('recreate', text)) + return self + + def finish(self): + with open(self.file_path, 'r') as src_file, open(self.file_path + '.new', 'w') as new_file: + for line in src_file.readlines(): + need_write = True + need_recreate = None + for cmd in self.commands: + if cmd[0] == 'rm_line' and cmd[1] in line: + need_write = False + break + + elif cmd[0] == 'rm_comment' and \ + (line.startswith('/**') or line.startswith(' * ') or line.startswith(' */')): + need_write = False + break + + elif cmd[0] == 'recreate': + need_recreate = cmd[1] + break + + elif cmd[0] == 'replace': + line = line.replace(cmd[1], cmd[2]) + + elif cmd[0] == 'replace_header' and (line.startswith('package') or line.startswith('import')): + line = line.replace(cmd[1], cmd[2]) + + if need_recreate is not None: + new_file.write(need_recreate) + break + + if need_write: + new_file.write(line) + + shutil.move(self.file_path + '.new', self.file_path) + + +class ProjectGenerator: + def __init__(self, template_zip, version): + self.template_zip = template_zip + self.version = version + + def generate_project(self, project_name, package_name): + template_dir = unzip_src_package(self.template_zip) + + if os.path.exists(project_name): + shutil.rmtree(project_name) + shutil.move(template_dir, project_name) + + os.chdir(project_name) + shutil.move('sample', 'app') + + print 'Creating project [%s]...' % project_name + self.process(project_name, package_name) + print 'Creat finished.' + + def process(self, project_name, package_name): + # ================= + # Root + # ================= + # build.gradle + TextProcesser('build.gradle').rm_line_has_text('android-maven').finish() + + # settings.gradle + TextProcesser('settings.gradle').recreate("include ':app', ':data'").finish() + + # rm unnessary files + os.remove('README.md') + os.remove('.gitattributes') + shutil.rmtree('art') + if os.path.exists('project_generator.py'): + os.remove('project_generator.py') + + # ================= + # app + # ================= + # build.gradle + TextProcesser('app/build.gradle') \ + .replace_all_text('cn.nekocode.template', package_name) \ + .finish() + + # build.gradle + TextProcesser('app/proguard-rules.pro') \ + .replace_all_text('cn.nekocode.template', package_name) \ + .finish() + + # AndroidManifest.xml + TextProcesser('app/src/main/AndroidManifest.xml') \ + .replace_all_text('cn.nekocode.template', package_name) \ + .finish() + + # strings.xml + TextProcesser('app/src/main/res/values/strings.xml') \ + .replace_all_text('Kotlin-Android', project_name) \ + .finish() + + # move package + package_dir_postfix = package_name.replace('.', '/') + tmp_package_path = 'app/src/main/javaTmp/' + package_dir_postfix + '/' + old_package_path = 'app/src/main/java/cn/nekocode/template/' + os.makedirs(tmp_package_path) + + for f in os.listdir(old_package_path): + shutil.move(old_package_path + f, tmp_package_path) + shutil.rmtree('app/src/main/java') + + os.renames('app/src/main/javaTmp', 'app/src/main/java') + + new_package_path = 'app/src/main/java/' + package_dir_postfix + '/' + + # src files + def process_all_src(path): + for p in os.listdir(path): + if os.path.isdir(path + p): + process_all_src(path + p + '/') + + elif p.endswith('.kt') or p.endswith('.java'): + TextProcesser(path + p) \ + .remove_comment() \ + .replace_header('cn.nekocode.template', package_name) \ + .finish() + + process_all_src(new_package_path) + + # ================= + # data + # ================= + package_name += '.data' + + # AndroidManifest.xml + TextProcesser('data/src/main/AndroidManifest.xml') \ + .replace_all_text('cn.nekocode.template.data', package_name) \ + .finish() + + # move package + package_dir_postfix = package_name.replace('.', '/') + tmp_package_path = 'data/src/main/javaTmp/' + package_dir_postfix + '/' + old_package_path = 'data/src/main/java/cn/nekocode/template/data/' + os.makedirs(tmp_package_path) + + for f in os.listdir(old_package_path): + shutil.move(old_package_path + f, tmp_package_path) + shutil.rmtree('data/src/main/java') + + os.renames('data/src/main/javaTmp', 'data/src/main/java') + + new_package_path = 'data/src/main/java/' + package_dir_postfix + '/' + + # move test package + package_dir_postfix = package_name.replace('.', '/') + tmp_package_path = 'data/src/test/javaTmp/' + package_dir_postfix + '/' + old_package_path = 'data/src/test/java/cn/nekocode/template/data/' + os.makedirs(tmp_package_path) + + for f in os.listdir(old_package_path): + shutil.move(old_package_path + f, tmp_package_path) + shutil.rmtree('data/src/test/java') + + os.renames('data/src/test/javaTmp', 'data/src/test/java') + + new_test_package_path = 'data/src/test/java/' + package_dir_postfix + '/' + + # src files + def process_all_src(path): + for p in os.listdir(path): + if os.path.isdir(path + p): + process_all_src(path + p + '/') + + elif p.endswith('.kt') or p.endswith('.java'): + TextProcesser(path + p) \ + .remove_comment() \ + .replace_header('cn.nekocode.template.data', package_name) \ + .finish() + + process_all_src(new_package_path) + process_all_src(new_test_package_path) + + return self + + +def main(): + project_name = raw_input('Input new project name: ') + package_path = raw_input('Input the full package path (such as com.company.test): ') + + template_zip, version = download_lastest_src() + factory = ProjectGenerator(template_zip, version) + factory.generate_project(project_name, package_path) + + +if __name__ == '__main__' and requests is not None: + main() diff --git a/backend/.gitignore b/sample/.gitignore similarity index 100% rename from backend/.gitignore rename to sample/.gitignore diff --git a/sample/build.gradle b/sample/build.gradle new file mode 100644 index 0000000..9e6c22c --- /dev/null +++ b/sample/build.gradle @@ -0,0 +1,92 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' +apply plugin: 'kotlin-kapt' + +android { + compileSdkVersion(COMPILE_SDK_VERSION.toInteger()) + buildToolsVersion(BUILD_TOOLS_VERSION) + + defaultConfig { + minSdkVersion 17 + targetSdkVersion 25 + versionCode 1 + versionName "1.0.0" + } + + buildTypes { + debug { + applicationIdSuffix '.debug' + } + release { + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + + if (project.hasProperty('storeFile') && + project.hasProperty('storePassword') && + project.hasProperty('keyAlias') && + project.hasProperty('keyPassword')) { + + android.signingConfigs.release.storeFile = file(storeFile) + android.signingConfigs.release.storePassword = storePassword + android.signingConfigs.release.keyAlias = keyAlias + android.signingConfigs.release.keyPassword = keyPassword + } else { + + buildTypes.release.signingConfig = null + } + + lintOptions { + disable 'GoogleAppIndexingWarning', 'UnusedResources' // Lint still does not detect usage of resources from Kotlin code. + } +} + +dependencies { + compile fileTree(include: ['*.jar'], dir: 'libs') + + // Data Layer + debugCompile project(path: ':data', configuration: "debug") + releaseCompile project(path: ':data', configuration: "release") + + // Android support libraries + compile "com.android.support:appcompat-v7:$SUPPORT_LIBS_VERSION" + compile "com.android.support:recyclerview-v7:$SUPPORT_LIBS_VERSION" + compile "com.android.support:support-annotations:$SUPPORT_LIBS_VERSION" + + // Reactive library + compile "io.reactivex.rxjava2:rxkotlin:2.0.2" + compile "io.reactivex.rxjava2:rxandroid:2.0.1" + def rxLifecycleVersion = "2.0.1" + compile "com.trello.rxlifecycle2:rxlifecycle:$rxLifecycleVersion" + compile "com.trello.rxlifecycle2:rxlifecycle-android:$rxLifecycleVersion" + compile "com.trello.rxlifecycle2:rxlifecycle-components:$rxLifecycleVersion" + compile "com.trello.rxlifecycle2:rxlifecycle-kotlin:$rxLifecycleVersion" + + // DSL + def ankoVersion = "0.9.1" + compile "org.jetbrains.anko:anko-sdk15:$ankoVersion" + compile "org.jetbrains.anko:anko-support-v4:$ankoVersion" + compile "org.jetbrains.anko:anko-appcompat-v7:$ankoVersion" + + // Persist Instance State + compile "com.evernote:android-state:1.0.6" + kapt "com.evernote:android-state-processor:1.0.6" + + // Tools + compile "com.squareup.picasso:picasso:2.5.2" + compile "com.github.nekocode:Meepo:0.1.5" + compile "com.github.nekocode:ItemPool:0.5.2" + + compile "org.jetbrains.kotlin:kotlin-stdlib:$KOTLIN_VERSION" +} + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-android-extensions:$KOTLIN_VERSION" + } +} diff --git a/sample/proguard-rules.pro b/sample/proguard-rules.pro new file mode 100644 index 0000000..0c8e696 --- /dev/null +++ b/sample/proguard-rules.pro @@ -0,0 +1,98 @@ +# +# This ProGuard configuration file illustrates how to process Android +# applications. +# Usage: +# java -jar proguard.jar @android.pro +# +# If you're using the Android SDK, the Ant release build and Eclipse export +# already take care of the proper settings. You only need to enable ProGuard +# by commenting in the corresponding line in project.properties. You can still +# add project-specific configuration in proguard-project.txt. +# +# This configuration file is for custom, stand-alone builds. + +-ignorewarnings + +# Specify the input jars, output jars, and library jars. +# Note that ProGuard works with Java bytecode (.class), +# before the dex compiler converts it into Dalvik code (.dex). + +#-injars bin/classes +#-injars libs +#-outjars bin/classes-processed.jar + +#-libraryjars /usr/local/opt/android-sdk/platforms/android-23/android.jar +#-libraryjars /usr/local/android-sdk/add-ons/google_apis-7_r01/libs/maps.jar +# ... + +# Save the obfuscation mapping to a file, so you can de-obfuscate any stack +# traces later on. + +-printmapping build/mapping.txt + +# You can print out the seeds that are matching the keep options below. + +#-printseeds bin/classes-processed.seeds + +# Reduce the size of the output some more. + +-repackageclasses '' +-allowaccessmodification + +# Keep a fixed source file attribute and all line number tables to get line +# numbers in the stack traces. +# You can comment this out if you're not interested in stack traces. + +-renamesourcefileattribute SourceFile +-keepattributes SourceFile,LineNumberTable + +# Preserve annotated Javascript interface methods. + +-keepclassmembers class * { + @android.webkit.JavascriptInterface ; +} + +# Your application may contain more items that need to be preserved; +# typically classes that are dynamically created using Class.forName: + +# -keep public class mypackage.MyClass +# -keep public interface mypackage.MyInterface +# -keep public class * implements mypackage.MyInterface + +# If you wish, you can let the optimization step remove Android logging calls. + +-assumenosideeffects class android.util.Log { + public static int d(...); +} +# public static boolean isLoggable(java.lang.String, int); +# public static int v(...); +# public static int i(...); +# public static int w(...); +# public static int e(...); + +# Kotlin +-dontwarn kotlin.** +-keepclassmembers class **$WhenMappings { + ; +} +# If you want to get rid of null checks at runtime you may use the following rule: +#-assumenosideeffects class kotlin.jvm.internal.Intrinsics { +# static void checkParameterIsNotNull(java.lang.Object, java.lang.String); +#} + +# Retrofit2 +-dontnote retrofit2.Platform +-dontwarn retrofit2.Platform$Java8 +-keepattributes Signature +-keepattributes Exceptions +-dontwarn okio.** + +# Picasso +-dontwarn com.squareup.okhttp.** + +# Gson +-keep class com.google.gson.** { *; } +-keepattributes Signature + +# Keep your model classes +-keep class cn.nekocode.kotgo.sample.data.DO.** { *; } diff --git a/app/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml similarity index 52% rename from app/src/main/AndroidManifest.xml rename to sample/src/main/AndroidManifest.xml index 7dddd2a..8513b37 100644 --- a/app/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -1,37 +1,35 @@ + package="cn.nekocode.template"> - + + + + + android:theme="@style/AppTheme"> + + android:name=".screen.main.MainActivity" + android:label="@string/app_name"> + - + + android:name=".screen.page2.Page2Activity"/> - \ No newline at end of file + diff --git a/sample/src/main/java/cn/nekocode/template/App.kt b/sample/src/main/java/cn/nekocode/template/App.kt new file mode 100644 index 0000000..5716745 --- /dev/null +++ b/sample/src/main/java/cn/nekocode/template/App.kt @@ -0,0 +1,16 @@ +package cn.nekocode.template + +import android.app.Application +import cn.nekocode.template.data.DataLayer + +/** + * @author nekocode (nekocode.cn@gmail.com) + */ +class App : Application() { + + override fun onCreate() { + super.onCreate() + DataLayer.init(this) + } + +} diff --git a/sample/src/main/java/cn/nekocode/template/base/BaseActivity.kt b/sample/src/main/java/cn/nekocode/template/base/BaseActivity.kt new file mode 100644 index 0000000..057efe6 --- /dev/null +++ b/sample/src/main/java/cn/nekocode/template/base/BaseActivity.kt @@ -0,0 +1,49 @@ +package cn.nekocode.template.base + +import android.app.Fragment +import android.app.FragmentTransaction +import android.os.Bundle +import android.support.annotation.CallSuper +import com.trello.rxlifecycle2.components.support.RxAppCompatActivity + +/** + * @author nekocode (nekocode.cn@gmail.com) + */ +abstract class BaseActivity : RxAppCompatActivity(), IContextProvider { + + override fun context() = this + + abstract fun onCreatePresenter(presenterFactory: PresenterFactory) + + @CallSuper + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val trans = fragmentManager.beginTransaction() + onCreatePresenter(PresenterFactory(trans)) + trans.commit() + } + + fun addOrGetFragment( + trans: FragmentTransaction, containerId: Int, + tag: String, fragmentClass: Class, args: Bundle? = null): T { + + var fragment = fragmentManager.findFragmentByTag(tag) as T? + if (fragment == null || fragment.isDetached) { + fragment = Fragment.instantiate(this, fragmentClass.canonicalName, args) as T + + trans.add(containerId, fragment, tag) + } + + return fragment + } + + inner class PresenterFactory(val trans: FragmentTransaction) { + + fun > createOrGet(presenterClass: Class, args: Bundle? = null): T { + val _args = if (intent.extras != null) Bundle(intent.extras) else Bundle() + if (args != null) _args.putAll(args) + return addOrGetFragment(trans, 0, presenterClass.canonicalName, presenterClass, _args) + } + } +} \ No newline at end of file diff --git a/sample/src/main/java/cn/nekocode/template/base/BasePresenter.kt b/sample/src/main/java/cn/nekocode/template/base/BasePresenter.kt new file mode 100644 index 0000000..5673816 --- /dev/null +++ b/sample/src/main/java/cn/nekocode/template/base/BasePresenter.kt @@ -0,0 +1,32 @@ +package cn.nekocode.template.base + +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.trello.rxlifecycle2.components.RxFragment + +/** + * @author nekocode (nekocode.cn@gmail.com) + */ +abstract class BasePresenter : RxFragment(), IContextProvider { + var view: V? = null + + + override fun context(): Context? = activity + + final override fun onCreateView( + inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? { + + view = activity as V? + onViewCreated(view, savedInstanceState) + return null + } + + abstract fun onViewCreated(view: V?, savedInstanceState: Bundle?) + + final override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + } +} \ No newline at end of file diff --git a/sample/src/main/java/cn/nekocode/template/base/IContextProvider.kt b/sample/src/main/java/cn/nekocode/template/base/IContextProvider.kt new file mode 100644 index 0000000..1bca4e1 --- /dev/null +++ b/sample/src/main/java/cn/nekocode/template/base/IContextProvider.kt @@ -0,0 +1,11 @@ +package cn.nekocode.template.base + +import android.content.Context + +/** + * @author nekocode (nekocode.cn@gmail.com) + */ +interface IContextProvider { + + fun context(): Context? +} \ No newline at end of file diff --git a/sample/src/main/java/cn/nekocode/template/base/IPresenter.kt b/sample/src/main/java/cn/nekocode/template/base/IPresenter.kt new file mode 100644 index 0000000..def858a --- /dev/null +++ b/sample/src/main/java/cn/nekocode/template/base/IPresenter.kt @@ -0,0 +1,14 @@ +package cn.nekocode.template.base + +import cn.nekocode.template.screen.UIRouter +import org.jetbrains.anko.toast + +/** + * @author nekocode (nekocode.cn@gmail.com) + */ +interface IPresenter : IContextProvider, UIRouter { + + fun onError(err: Throwable) { + context()?.toast(err.message ?: "Unknown Error") + } +} \ No newline at end of file diff --git a/sample/src/main/java/cn/nekocode/template/base/IView.kt b/sample/src/main/java/cn/nekocode/template/base/IView.kt new file mode 100644 index 0000000..38c8167 --- /dev/null +++ b/sample/src/main/java/cn/nekocode/template/base/IView.kt @@ -0,0 +1,13 @@ +package cn.nekocode.template.base + +import org.jetbrains.anko.toast + +/** + * @author nekocode (nekocode.cn@gmail.com) + */ +interface IView : IContextProvider { + + fun toast(msg: String) { + context()?.toast(msg) + } +} \ No newline at end of file diff --git a/sample/src/main/java/cn/nekocode/template/item/MeiziItem.kt b/sample/src/main/java/cn/nekocode/template/item/MeiziItem.kt new file mode 100644 index 0000000..bb6bfb2 --- /dev/null +++ b/sample/src/main/java/cn/nekocode/template/item/MeiziItem.kt @@ -0,0 +1,39 @@ +package cn.nekocode.template.item + +import android.view.LayoutInflater +import android.view.ViewGroup +import cn.nekocode.itempool.Item +import cn.nekocode.template.R +import cn.nekocode.template.data.DO.Meizi +import com.squareup.picasso.Picasso +import kotlinx.android.synthetic.main.item_meizi.view.* + +/** + * @author nekocode (nekocode.cn@gmail.com) + */ +class MeiziItem : Item() { + + override fun onCreateItemView(inflater: LayoutInflater, parent: ViewGroup) = + inflater.inflate(R.layout.item_meizi, parent, false)!! + + override fun onBindItem(vo: MeiziItem.VO) { + with (viewHolder.itemView) { + Picasso.with(context).load(vo.url).centerCrop().fit().into(imageView) + textView.text = vo.title + textView2.text = vo.subTitle + } + } + + class VO( + val url: String, + val title: String, + val subTitle: String, + val DO: Any + ) { + companion object { + fun fromMeizi(meizi: Meizi): VO { + return VO(meizi.url, meizi.id, "${meizi.who} - ${meizi.type}", meizi) + } + } + } +} \ No newline at end of file diff --git a/sample/src/main/java/cn/nekocode/template/screen/UIRouter.kt b/sample/src/main/java/cn/nekocode/template/screen/UIRouter.kt new file mode 100644 index 0000000..43fbf19 --- /dev/null +++ b/sample/src/main/java/cn/nekocode/template/screen/UIRouter.kt @@ -0,0 +1,24 @@ +package cn.nekocode.template.screen + +import android.content.Context +import cn.nekocode.meepo.annotation.Bundle +import cn.nekocode.meepo.annotation.TargetClass +import cn.nekocode.meepo.Meepo +import cn.nekocode.template.data.DO.Meizi +import cn.nekocode.template.screen.page2.Page2Activity +import cn.nekocode.template.screen.page2.Page2Presenter + +/** + * @author nekocode (nekocode.cn@gmail.com) + */ +interface UIRouter { + + companion object { + val IMPL = Meepo.Builder().build().create(UIRouter::class.java) + } + + @TargetClass(Page2Activity::class) + fun gotoPage2(context: Context?, @Bundle(Page2Presenter.ARG_MEIZI) meizi: Meizi) { + IMPL.gotoPage2(context, meizi) + } +} \ No newline at end of file diff --git a/sample/src/main/java/cn/nekocode/template/screen/main/Contract.kt b/sample/src/main/java/cn/nekocode/template/screen/main/Contract.kt new file mode 100644 index 0000000..4a7ebf0 --- /dev/null +++ b/sample/src/main/java/cn/nekocode/template/screen/main/Contract.kt @@ -0,0 +1,18 @@ +package cn.nekocode.template.screen.main + +import cn.nekocode.itempool.ItemPool +import cn.nekocode.template.base.IPresenter +import cn.nekocode.template.base.IView + +/** + * @author nekocode (nekocode.cn@gmail.com) + */ +interface Contract { + + interface View : IView { + fun setItemPool(itemPool: ItemPool) + } + + interface Presenter: IPresenter { + } +} \ No newline at end of file diff --git a/sample/src/main/java/cn/nekocode/template/screen/main/MainActivity.kt b/sample/src/main/java/cn/nekocode/template/screen/main/MainActivity.kt new file mode 100644 index 0000000..14cb8c2 --- /dev/null +++ b/sample/src/main/java/cn/nekocode/template/screen/main/MainActivity.kt @@ -0,0 +1,32 @@ +package cn.nekocode.template.screen.main + +import android.os.Bundle +import android.support.v7.widget.LinearLayoutManager +import cn.nekocode.itempool.ItemPool +import cn.nekocode.template.R +import cn.nekocode.template.base.BaseActivity +import kotlinx.android.synthetic.main.activity_main.* + +/** + * @author nekocode (nekocode.cn@gmail.com) + */ +class MainActivity : BaseActivity(), Contract.View { + var presenter: Contract.Presenter? = null + + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + title = "Meizi List" + recyclerView.layoutManager = LinearLayoutManager(this) + } + + override fun onCreatePresenter(presenterFactory: PresenterFactory) { + presenter = presenterFactory.createOrGet(MainPresenter::class.java) + } + + override fun setItemPool(itemPool: ItemPool) { + itemPool.attachTo(recyclerView) + } +} diff --git a/sample/src/main/java/cn/nekocode/template/screen/main/MainPresenter.kt b/sample/src/main/java/cn/nekocode/template/screen/main/MainPresenter.kt new file mode 100644 index 0000000..e5ccb51 --- /dev/null +++ b/sample/src/main/java/cn/nekocode/template/screen/main/MainPresenter.kt @@ -0,0 +1,67 @@ +package cn.nekocode.template.screen.main + +import android.os.Bundle +import cn.nekocode.itempool.Item +import cn.nekocode.itempool.ItemPool +import cn.nekocode.template.base.BasePresenter +import cn.nekocode.template.data.DO.Meizi +import cn.nekocode.template.data.service.GankService +import cn.nekocode.template.item.MeiziItem +import com.evernote.android.state.State +import com.evernote.android.state.StateSaver +import com.trello.rxlifecycle2.kotlin.bindToLifecycle +import io.reactivex.Observable +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers +import java.util.* + +/** + * @author nekocode (nekocode.cn@gmail.com) + */ +class MainPresenter : BasePresenter(), Contract.Presenter { + @State + var list: ArrayList? = null + var itemPool = ItemPool() + + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + StateSaver.restoreInstanceState(this, savedInstanceState) + + itemPool.addType(MeiziItem::class.java) + itemPool.onEvent(MeiziItem::class.java) { event -> + val meizi = (event.data as MeiziItem.VO).DO as Meizi + when (event.action) { + Item.EVENT_ITEM_CLICK -> { + gotoPage2(context(), meizi) + } + } + } + } + + override fun onViewCreated(view: Contract.View?, savedInstanceState: Bundle?) { + if (list == null) { + GankService.getMeizis(50, 1) + } else { + Observable.just(list!!) + } + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.io()) + .map { meizis -> + list = meizis + meizis.map { MeiziItem.VO.fromMeizi(it) } + } + .bindToLifecycle(this) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ + itemPool.clear() + itemPool.addAll(it) + view?.setItemPool(itemPool) + }, this::onError) + } + + override fun onSaveInstanceState(outState: Bundle?) { + super.onSaveInstanceState(outState) + StateSaver.saveInstanceState(this, outState ?: return) + } +} \ No newline at end of file diff --git a/sample/src/main/java/cn/nekocode/template/screen/page2/Contract.kt b/sample/src/main/java/cn/nekocode/template/screen/page2/Contract.kt new file mode 100644 index 0000000..44270df --- /dev/null +++ b/sample/src/main/java/cn/nekocode/template/screen/page2/Contract.kt @@ -0,0 +1,18 @@ +package cn.nekocode.template.screen.page2 + +import cn.nekocode.template.base.IPresenter +import cn.nekocode.template.base.IView + +/** + * @author nekocode (nekocode.cn@gmail.com) + */ +interface Contract { + + interface View : IView { + fun setMeizi(vo: MeiziVO) + } + + interface Presenter: IPresenter { + fun onImageClick() + } +} \ No newline at end of file diff --git a/sample/src/main/java/cn/nekocode/template/screen/page2/MeiziVO.kt b/sample/src/main/java/cn/nekocode/template/screen/page2/MeiziVO.kt new file mode 100644 index 0000000..55d2df7 --- /dev/null +++ b/sample/src/main/java/cn/nekocode/template/screen/page2/MeiziVO.kt @@ -0,0 +1,18 @@ +package cn.nekocode.template.screen.page2 + +import cn.nekocode.template.data.DO.Meizi + +/** + * @author nekocode (nekocode.cn@gmail.com) + */ +class MeiziVO( + val url: String, + val title: String, + val DO: Any +) { + companion object { + fun fromMeizi(meizi: Meizi): MeiziVO { + return MeiziVO(meizi.url, meizi.id, meizi) + } + } +} \ No newline at end of file diff --git a/sample/src/main/java/cn/nekocode/template/screen/page2/Page2Activity.kt b/sample/src/main/java/cn/nekocode/template/screen/page2/Page2Activity.kt new file mode 100644 index 0000000..f6a4801 --- /dev/null +++ b/sample/src/main/java/cn/nekocode/template/screen/page2/Page2Activity.kt @@ -0,0 +1,34 @@ +package cn.nekocode.template.screen.page2 + +import android.os.Bundle +import cn.nekocode.template.R +import cn.nekocode.template.base.BaseActivity +import com.squareup.picasso.Picasso +import kotlinx.android.synthetic.main.activity_page2.* +import org.jetbrains.anko.onClick + +/** + * @author nekocode (nekocode.cn@gmail.com) + */ +class Page2Activity : BaseActivity(), Contract.View { + var presenter: Contract.Presenter? = null + + + override fun onCreatePresenter(presenterFactory: PresenterFactory) { + presenter = presenterFactory.createOrGet(Page2Presenter::class.java) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_page2) + + imageView.onClick { + presenter?.onImageClick() + } + } + + override fun setMeizi(vo: MeiziVO) { + title = vo.title + Picasso.with(this).load(vo.url).centerCrop().fit().into(imageView) + } +} \ No newline at end of file diff --git a/sample/src/main/java/cn/nekocode/template/screen/page2/Page2Presenter.kt b/sample/src/main/java/cn/nekocode/template/screen/page2/Page2Presenter.kt new file mode 100644 index 0000000..9d0f1e5 --- /dev/null +++ b/sample/src/main/java/cn/nekocode/template/screen/page2/Page2Presenter.kt @@ -0,0 +1,23 @@ +package cn.nekocode.template.screen.page2 + +import android.os.Bundle +import cn.nekocode.template.base.BasePresenter +import cn.nekocode.template.data.DO.Meizi + +/** + * @author nekocode (nekocode.cn@gmail.com) + */ +class Page2Presenter : BasePresenter(), Contract.Presenter { + companion object { + const val ARG_MEIZI = "ARG_MEIZI" + } + + override fun onViewCreated(view: Contract.View?, savedInstanceState: Bundle?) { + val meizi = arguments.getParcelable(ARG_MEIZI) + view?.setMeizi(MeiziVO.fromMeizi(meizi)) + } + + override fun onImageClick() { + activity.finish() + } +} \ No newline at end of file diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..d3cfe6d --- /dev/null +++ b/sample/src/main/res/layout/activity_main.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/sample/src/main/res/layout/activity_page2.xml b/sample/src/main/res/layout/activity_page2.xml new file mode 100644 index 0000000..3720a61 --- /dev/null +++ b/sample/src/main/res/layout/activity_page2.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/sample/src/main/res/layout/item_meizi.xml b/sample/src/main/res/layout/item_meizi.xml new file mode 100644 index 0000000..13e22d0 --- /dev/null +++ b/sample/src/main/res/layout/item_meizi.xml @@ -0,0 +1,42 @@ + + + + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/mipmap-hdpi/ic_launcher.png b/sample/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..cde69bc Binary files /dev/null and b/sample/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/sample/src/main/res/mipmap-mdpi/ic_launcher.png b/sample/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..c133a0c Binary files /dev/null and b/sample/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/sample/src/main/res/mipmap-xhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..bfa42f0 Binary files /dev/null and b/sample/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..324e72c Binary files /dev/null and b/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/sample/src/main/res/values-w820dp/dimens.xml b/sample/src/main/res/values-w820dp/dimens.xml new file mode 100644 index 0000000..63fc816 --- /dev/null +++ b/sample/src/main/res/values-w820dp/dimens.xml @@ -0,0 +1,6 @@ + + + 64dp + diff --git a/sample/src/main/res/values/colors.xml b/sample/src/main/res/values/colors.xml new file mode 100644 index 0000000..d4cdaea --- /dev/null +++ b/sample/src/main/res/values/colors.xml @@ -0,0 +1,30 @@ + + + #3F51B5 + #303F9F + #FF4081 + + #f1f2f1 + #F2F2F2 + #ededed + #ebeceb + #EAEAEA + #e4e4e4 + #d5d5d5 + #D2D2D2 + #cecece + #c2c2c2 + #C9C9C9 + #c8c8c8 + #BEBEBE + #BDBDBD + #A4A4A4 + #818181 + #7A7A7A + #767676 + #666666 + #656565 + #464646 + #000000 + #ffffff + diff --git a/sample/src/main/res/values/dimens.xml b/sample/src/main/res/values/dimens.xml new file mode 100644 index 0000000..47c8224 --- /dev/null +++ b/sample/src/main/res/values/dimens.xml @@ -0,0 +1,5 @@ + + + 16dp + 16dp + diff --git a/sample/src/main/res/values/strings.xml b/sample/src/main/res/values/strings.xml new file mode 100644 index 0000000..be3a294 --- /dev/null +++ b/sample/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Kotlin-Android + diff --git a/sample/src/main/res/values/styles.xml b/sample/src/main/res/values/styles.xml new file mode 100644 index 0000000..4f7b756 --- /dev/null +++ b/sample/src/main/res/values/styles.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..824d9e6 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include ':sample', ':data' diff --git a/settings.gradle.kts b/settings.gradle.kts deleted file mode 100644 index 0a28e1a..0000000 --- a/settings.gradle.kts +++ /dev/null @@ -1 +0,0 @@ -include(":app", ":backend")