- Fastlane completed

- Circleci config completed
 - Flavours build completed
This commit is contained in:
2023-08-07 15:01:51 +01:00
parent 377970a3fc
commit 03ee1dc249
29 changed files with 343 additions and 237 deletions

View File

@@ -15,9 +15,15 @@ commands:
steps: steps:
- checkout - checkout
- android/restore-gradle-cache - android/restore-gradle-cache
build_gradle:
description: Build the gradle
steps:
- android/restore-gradle-cache
- run: - run:
name: Download Dependencies name: Download Dependencies
command: ./gradlew androidDependencies command: |
sudo chmod +x ./gradlew
./gradlew androidDependencies
- android/save-gradle-cache - android/save-gradle-cache
run_tests: run_tests:
description: run tests for flavour specified description: run tests for flavour specified
@@ -26,14 +32,17 @@ commands:
type: string type: string
default: "AtlasWeather" default: "AtlasWeather"
steps: steps:
# The next step will run the unit tests # The next step will run the unit tests
- android/run-tests: - build_gradle
test-command: ./gradlew test<< parameters.flavour >>DebugUnitTest --continue - run:
- store_artifacts: name: Run non-instrumentation unit tests
path: app/build/reports command: |
destination: reports ./gradlew test<< parameters.flavour >>DebugUnitTest --continue
- store_test_results: - store_artifacts:
path: app/build/test-results path: app/build/reports
destination: reports
- store_test_results:
path: app/build/test-results
run_ui_tests: run_ui_tests:
description: run tests for flavour specified description: run tests for flavour specified
parameters: parameters:
@@ -41,27 +50,44 @@ commands:
type: string type: string
default: "AtlasWeather" default: "AtlasWeather"
steps: steps:
- android/start-emulator-and-run-tests: - build_gradle
post-emulator-launch-assemble-command: ./gradlew assemble<< parameters.flavour >>DebugAndroidTest - android/start-emulator-and-run-tests:
test-command: ./gradlew connected<< parameters.flavour >>DebugAndroidTest post-emulator-launch-assemble-command: ./gradlew assemble<< parameters.flavour >>DebugAndroidTest
system-image: system-images;android-25;google_apis;x86 test-command: ./gradlew connected<< parameters.flavour >>DebugAndroidTest
max-tries: 1 system-image: system-images;android-25;google_apis;x86
kill-emulators: false pull-data: true
- run: pull-data-path: /storage/emulated/0/Android/data/
name: Pull screenshots from device pull-data-target: ~/app-data
command: | # store test reports
mkdir ~/screenshots - store_artifacts:
adb pull /storage/emulated/0/Android/data/com.appttude.h_mal.atlas_weather/files/screengrab/en-US/images/screenshots ~/screenshots path: app/build/reports/androidTests/connected
when: on_fail destination: reports
# store test reports # store screenshots for failed ui tests
- store_artifacts: - store_artifacts:
path: app/build/reports/androidTests/connected path: ~/app-data
destination: reports destination: screenshots
# store screenshots for failed ui tests deploy_to_play_store:
- store_artifacts: description: deploy to playstore based on flavour
path: ~/screenshots parameters:
destination: screenshots flavour:
type: string
default: "AtlasWeather"
steps:
# The next step will run the unit tests
- android/decode-keystore:
keystore-location: "./app/keystore.jks"
- run:
name: Setup playstore key
command: |
echo "$GOOGLE_PLAY_KEY" > "google-play-key.json"
- build_gradle
- run:
name: Run fastlane command to deploy to playstore
command: |
pwd
bundle exec fastlane deploy<< parameters.flavour >>
- store_test_results:
path: fastlane/report.xml
# Define a job to be invoked later in a workflow. # Define a job to be invoked later in a workflow.
# See: https://circleci.com/docs/2.0/configuration-reference/#jobs # See: https://circleci.com/docs/2.0/configuration-reference/#jobs
jobs: jobs:
@@ -83,54 +109,60 @@ jobs:
- setup_repo - setup_repo
- run_tests: - run_tests:
flavour: << parameters.flavour >> flavour: << parameters.flavour >>
ui-test-and-release: - run_ui_tests:
# Parameters used for determining flavour: << parameters.flavour >>
deploy-to-playstore:
parameters: parameters:
flavour: flavour:
type: string type: string
default: "AtlasWeather" default: "Driver"
executor: docker:
name: android/android-machine - image: cimg/android:2023.07-browsers
tag: 2023.05.1 auth:
username: ${DOCKER_USERNAME}
password: ${DOCKER_PASSWORD}
steps: steps:
- setup_repo - setup_repo
- run_ui_tests - deploy_to_play_store:
- run: flavour: << parameters.flavour >>
name: Setup variables for release
command: |
echo "$RELEASE_KEYSTORE_BASE64" | base64 --decode > "android/app/release_keystore.jks"
echo "$GOOGLE_PLAY_KEY" > "android/playstore.json"
# And finally run the release build
- run:
name: Assemble and Upload to PlayStore
command: |
pwd
bundle exec fastlane deploy<< parameters.flavour >>
# Invoke jobs via workflows # Invoke jobs via workflows
# See: https://circleci.com/docs/2.0/configuration-reference/#workflows # See: https://circleci.com/docs/2.0/configuration-reference/#workflows
workflows: workflows:
version: 2 version: 2
build-release-mono:
jobs:
- build-and-test:
context: appttude
flavour: "MonoWeather"
filters:
branches:
ignore:
- main_atlas
- deploy-to-playstore:
context: appttude
flavour: "MonoWeather"
filters:
branches:
only:
- main_mono
requires:
- build-and-test
build-release-atlas: build-release-atlas:
jobs: jobs:
- build-and-test: - build-and-test:
context: appttude
flavour: "AtlasWeather" flavour: "AtlasWeather"
- ui-test-and-release: filters:
branches:
ignore:
- main_mono
- deploy-to-playstore:
context: appttude
flavour: "AtlasWeather" flavour: "AtlasWeather"
filters: filters:
branches: branches:
only: only:
- main_atlas - main_atlas
requires:
- build-and-test
build-release-mono:
jobs:
- build-and-test:
flavour: "MonoWeather"
- ui-test-and-release:
flavour: "MonoWeather"
filters:
branches:
only: main_admin
requires: requires:
- build-and-test - build-and-test

View File

@@ -16,6 +16,19 @@
</AndroidTestResultsTableState> </AndroidTestResultsTableState>
</value> </value>
</entry> </entry>
<entry key="-2008434490">
<value>
<AndroidTestResultsTableState>
<option name="preferredColumnWidths">
<map>
<entry key="Duration" value="90" />
<entry key="Pixel_2_API_27" value="120" />
<entry key="Tests" value="360" />
</map>
</option>
</AndroidTestResultsTableState>
</value>
</entry>
<entry key="170536241"> <entry key="170536241">
<value> <value>
<AndroidTestResultsTableState> <AndroidTestResultsTableState>
@@ -29,6 +42,19 @@
</AndroidTestResultsTableState> </AndroidTestResultsTableState>
</value> </value>
</entry> </entry>
<entry key="287238248">
<value>
<AndroidTestResultsTableState>
<option name="preferredColumnWidths">
<map>
<entry key="Duration" value="90" />
<entry key="Pixel_2_API_27" value="120" />
<entry key="Tests" value="360" />
</map>
</option>
</AndroidTestResultsTableState>
</value>
</entry>
<entry key="1127175145"> <entry key="1127175145">
<value> <value>
<AndroidTestResultsTableState> <AndroidTestResultsTableState>

View File

@@ -4,15 +4,19 @@ import android.Manifest
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.View
import androidx.test.core.app.ActivityScenario import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.internal.inject.InstrumentationContext import androidx.test.espresso.Espresso
import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.GrantPermissionRule import androidx.test.rule.GrantPermissionRule
import com.appttude.h_mal.atlas_weather.application.TestAppClass import com.appttude.h_mal.atlas_weather.application.TestAppClass
import com.appttude.h_mal.atlas_weather.helper.GenericsHelper.getGenericClassAt
import com.appttude.h_mal.atlas_weather.helpers.SnapshotRule import com.appttude.h_mal.atlas_weather.helpers.SnapshotRule
import com.appttude.h_mal.atlas_weather.utils.Stubs import com.appttude.h_mal.atlas_weather.utils.Stubs
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.hamcrest.Matcher
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
@@ -26,6 +30,7 @@ open class BaseTest<A : Activity>(
lateinit var scenario: ActivityScenario<A> lateinit var scenario: ActivityScenario<A>
private lateinit var testApp: TestAppClass private lateinit var testApp: TestAppClass
private lateinit var testActivity: Activity
@get:Rule @get:Rule
var permissionRule = GrantPermissionRule.grant(Manifest.permission.ACCESS_COARSE_LOCATION) var permissionRule = GrantPermissionRule.grant(Manifest.permission.ACCESS_COARSE_LOCATION)
@@ -36,19 +41,22 @@ open class BaseTest<A : Activity>(
@Before @Before
fun setUp() { fun setUp() {
Screengrab.setDefaultScreenshotStrategy(UiAutomatorScreenshotStrategy()) Screengrab.setDefaultScreenshotStrategy(UiAutomatorScreenshotStrategy())
val startIntent = Intent(InstrumentationRegistry.getInstrumentation().targetContext, activity) val startIntent =
Intent(InstrumentationRegistry.getInstrumentation().targetContext, activity)
if (intentBundle != null) { if (intentBundle != null) {
startIntent.replaceExtras(intentBundle) startIntent.replaceExtras(intentBundle)
} }
testApp = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as TestAppClass
runBlocking {
beforeLaunch()
}
scenario = ActivityScenario.launch(startIntent) scenario = ActivityScenario.launch(startIntent)
scenario.onActivity { scenario.onActivity {
runBlocking { testActivity = it
testApp = it.application as TestAppClass afterLaunch()
beforeLaunch()
}
} }
afterLaunch()
} }
fun stubEndpoint(url: String, stub: Stubs) { fun stubEndpoint(url: String, stub: Stubs) {
@@ -59,6 +67,8 @@ open class BaseTest<A : Activity>(
testApp.removeUrlStub(url) testApp.removeUrlStub(url)
} }
fun getActivity() = testActivity
@After @After
fun tearDown() { fun tearDown() {
testFinished() testFinished()
@@ -67,4 +77,14 @@ open class BaseTest<A : Activity>(
open fun beforeLaunch() {} open fun beforeLaunch() {}
open fun afterLaunch() {} open fun afterLaunch() {}
open fun testFinished() {} open fun testFinished() {}
fun waitFor(delay: Long) {
Espresso.onView(ViewMatchers.isRoot()).perform(object : ViewAction {
override fun getConstraints(): Matcher<View> = ViewMatchers.isRoot()
override fun getDescription(): String = "wait for $delay milliseconds"
override fun perform(uiController: UiController, v: View?) {
uiController.loopMainThreadForAtLeast(delay)
}
})
}
} }

View File

@@ -1,6 +1,5 @@
package com.appttude.h_mal.atlas_weather.data.location package com.appttude.h_mal.atlas_weather.data.location
import com.appttude.h_mal.atlas_weather.data.location.LocationProvider
import com.appttude.h_mal.atlas_weather.model.types.LocationType import com.appttude.h_mal.atlas_weather.model.types.LocationType
class MockLocationProvider : LocationProvider { class MockLocationProvider : LocationProvider {

View File

@@ -7,7 +7,7 @@ import com.appttude.h_mal.atlas_weather.ui.MainActivity
import com.appttude.h_mal.atlas_weather.utils.Stubs import com.appttude.h_mal.atlas_weather.utils.Stubs
import org.junit.Test import org.junit.Test
class HomePageUITest : BaseTest<MainActivity>(activity = MainActivity::class.java) { class HomePageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
override fun beforeLaunch() { override fun beforeLaunch() {
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Valid) stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Valid)
@@ -16,6 +16,7 @@ class HomePageUITest : BaseTest<MainActivity>(activity = MainActivity::class.jav
@Test @Test
fun loadApp_validWeatherResponse_returnsValidPage() { fun loadApp_validWeatherResponse_returnsValidPage() {
homeScreen { homeScreen {
waitFor(2000)
verifyCurrentTemperature(2) verifyCurrentTemperature(2)
verifyCurrentLocation("Mock Location") verifyCurrentLocation("Mock Location")
} }

View File

@@ -1,8 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools">
package="com.appttude.h_mal.atlas_weather"
tools:node="merge">
<application <application
android:name="com.appttude.h_mal.atlas_weather.application.AppClass" android:name="com.appttude.h_mal.atlas_weather.application.AppClass"

View File

@@ -1,55 +0,0 @@
package com.appttude.h_mal.atlas_weather.ui
import android.annotation.SuppressLint
import android.content.pm.PackageManager
import android.view.View
import androidx.core.app.ActivityCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import com.appttude.h_mal.atlas_weather.utils.Event
import com.appttude.h_mal.atlas_weather.utils.displayToast
import com.appttude.h_mal.atlas_weather.utils.hide
import com.appttude.h_mal.atlas_weather.utils.show
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
abstract class BaseFragment: Fragment(){
// toggle visibility of progress spinner while async operations are taking place
fun progressBarStateObserver(progressBar: View) = Observer<Event<Boolean>> {
when(it.getContentIfNotHandled()){
true -> {
progressBar.show()
}
false -> {
progressBar.hide()
}
}
}
// display a toast when operation fails
fun errorObserver() = Observer<Event<String>> {
it.getContentIfNotHandled()?.let { message ->
displayToast(message)
}
}
@SuppressLint("MissingPermission")
fun getPermissionResult(
permission: String,
permissionCode: Int,
permissionGranted: () -> Unit
){
if (ActivityCompat.checkSelfPermission(requireContext(), permission) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(arrayOf(permission), permissionCode)
return
}else{
CoroutineScope(Dispatchers.Main).launch{
permissionGranted.invoke()
}
}
}
}

View File

@@ -0,0 +1,22 @@
package com.appttude.h_mal.atlas_weather.ui.dialog
import android.content.Context
import android.content.res.Resources
import android.os.Build
import android.text.Html
import androidx.annotation.RequiresApi
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
interface DeclarationBuilder{
val link: String
val message: String
fun Context.readFromResources(@StringRes id: Int) = resources.getString(id)
fun buildMessage(): CharSequence? {
val link1 = "<font color='blue'><a href=\"$link\">here</a></font>"
val message = "$message See my privacy policy: $link1"
return Html.fromHtml(message, Html.FROM_HTML_MODE_LEGACY)
}
}

View File

@@ -0,0 +1,45 @@
package com.appttude.h_mal.atlas_weather.ui.dialog
import android.content.Context
import android.text.method.LinkMovementMethod
import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
class PermissionsDeclarationDialog(context: Context) : BaseDeclarationDialog(context) {
override val link: String = "https://sites.google.com/view/hmaldev/home/monochrome"
override val message: String = "Hi, thank you for downloading my app. Google play isn't letting me upload my app to the Playstore until I have a privacy declaration :(. My app is basically used to demonstrate my code=ing to potential employers and others. I do NOT store or process any information. The location permission in the app is there just to provide the end user with weather data."
}
abstract class BaseDeclarationDialog(val context: Context): DeclarationBuilder {
abstract override val link: String
abstract override val message: String
lateinit var dialog: AlertDialog
fun showDialog(agreeCallback: () -> Unit = { }, disagreeCallback: () -> Unit = { }) {
val myMessage = buildMessage()
val builder = AlertDialog.Builder(context)
.setPositiveButton("agree") { _, _ ->
agreeCallback()
}
.setNegativeButton("disagree") { _, _ ->
disagreeCallback()
}
.setMessage(myMessage)
.setCancelable(false)
dialog = builder.create()
dialog.show()
// Make the textview clickable. Must be called after show()
val msgTxt = dialog.findViewById<View>(android.R.id.message) as TextView?
msgTxt?.movementMethod = LinkMovementMethod.getInstance()
}
fun dismiss() = dialog.dismiss()
}

View File

@@ -1,24 +1,27 @@
package com.appttude.h_mal.atlas_weather.ui.home package com.appttude.h_mal.atlas_weather.ui.home
import android.Manifest import android.Manifest
import android.Manifest.permission.ACCESS_COARSE_LOCATION
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.pm.PackageManager
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.observe import androidx.navigation.Navigation.findNavController
import androidx.navigation.ui.onNavDestinationSelected
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.appttude.h_mal.atlas_weather.R import com.appttude.h_mal.atlas_weather.R
import com.appttude.h_mal.atlas_weather.application.LOCATION_PERMISSION_REQUEST import com.appttude.h_mal.atlas_weather.application.LOCATION_PERMISSION_REQUEST
import com.appttude.h_mal.atlas_weather.ui.BaseFragment import com.appttude.h_mal.atlas_weather.model.forecast.Forecast
import com.appttude.h_mal.atlas_weather.ui.dialog.PermissionsDeclarationDialog
import com.appttude.h_mal.atlas_weather.ui.home.adapter.WeatherRecyclerAdapter import com.appttude.h_mal.atlas_weather.ui.home.adapter.WeatherRecyclerAdapter
import com.appttude.h_mal.atlas_weather.utils.displayToast
import com.appttude.h_mal.atlas_weather.utils.navigateTo import com.appttude.h_mal.atlas_weather.utils.navigateTo
import com.appttude.h_mal.atlas_weather.viewmodel.ApplicationViewModelFactory import com.appttude.h_mal.atlas_weather.viewmodel.ApplicationViewModelFactory
import com.appttude.h_mal.atlas_weather.viewmodel.MainViewModel import com.appttude.h_mal.atlas_weather.viewmodel.MainViewModel
import com.appttude.h_mal.monoWeather.ui.BaseFragment
import kotlinx.android.synthetic.main.fragment_home.* import kotlinx.android.synthetic.main.fragment_home.*
import org.kodein.di.KodeinAware import org.kodein.di.KodeinAware
import org.kodein.di.android.x.kodein import org.kodein.di.android.x.kodein
@@ -30,43 +33,29 @@ import org.kodein.di.generic.instance
* A simple [Fragment] subclass. * A simple [Fragment] subclass.
* create an instance of this fragment. * create an instance of this fragment.
*/ */
class HomeFragment : BaseFragment(), KodeinAware { class HomeFragment : BaseFragment(R.layout.fragment_home) {
override val kodein by kodein() private val viewModel by getFragmentViewModel<MainViewModel>()
private val factory by instance<ApplicationViewModelFactory>()
private val viewModel by activityViewModels<MainViewModel> { factory }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_home, container, false)
}
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setHasOptionsMenu(true)
val recyclerAdapter = WeatherRecyclerAdapter { val recyclerAdapter = WeatherRecyclerAdapter(itemClick = {
val directions = navigateToFurtherDetails(it)
HomeFragmentDirections.actionHomeFragmentToFurtherDetailsFragment(it) })
navigateTo(directions)
}
forecast_listview.apply { forecast_listview.apply {
layoutManager = LinearLayoutManager(context) layoutManager = LinearLayoutManager(context)
adapter = recyclerAdapter adapter = recyclerAdapter
} }
getPermissionResult(Manifest.permission.ACCESS_FINE_LOCATION, LOCATION_PERMISSION_REQUEST){
viewModel.fetchData()
}
swipe_refresh.apply { swipe_refresh.apply {
setOnRefreshListener { setOnRefreshListener {
getPermissionResult(Manifest.permission.ACCESS_FINE_LOCATION, LOCATION_PERMISSION_REQUEST){ getPermissionResult(ACCESS_COARSE_LOCATION, LOCATION_PERMISSION_REQUEST) {
viewModel.fetchData() viewModel.fetchData()
isRefreshing = true
} }
isRefreshing = true
} }
} }
@@ -76,24 +65,45 @@ class HomeFragment : BaseFragment(), KodeinAware {
viewModel.operationState.observe(viewLifecycleOwner, progressBarStateObserver(progressBar)) viewModel.operationState.observe(viewLifecycleOwner, progressBarStateObserver(progressBar))
viewModel.operationError.observe(viewLifecycleOwner, errorObserver()) viewModel.operationError.observe(viewLifecycleOwner, errorObserver())
viewModel.operationRefresh.observe(viewLifecycleOwner) { it ->
it.getContentIfNotHandled()?.let {
swipe_refresh.isRefreshing = false
}
}
viewModel.operationState.observe(viewLifecycleOwner){ viewModel.operationState.observe(viewLifecycleOwner) {
swipe_refresh.isRefreshing = false swipe_refresh.isRefreshing = false
} }
} }
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String?>, grantResults: IntArray) { override fun onStart() {
super.onRequestPermissionsResult(requestCode, permissions, grantResults) super.onStart()
if (requestCode == LOCATION_PERMISSION_REQUEST) {
if (grantResults.isNotEmpty() getPermissionResult(ACCESS_COARSE_LOCATION, LOCATION_PERMISSION_REQUEST) {
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) { viewModel.fetchData()
viewModel.fetchData()
displayToast("Permission granted")
} else {
displayToast("Permission denied")
}
} }
} }
@SuppressLint("MissingPermission")
override fun permissionsGranted() {
viewModel.fetchData()
}
private fun navigateToFurtherDetails(forecast: Forecast) {
val directions = HomeFragmentDirections
.actionHomeFragmentToFurtherDetailsFragment(forecast)
navigateTo(directions)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
// Inflate the menu; this adds items to the action bar if it is present.
inflater.inflate(R.menu.menu_main, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val navController = findNavController(requireActivity(), R.id.container)
return item.onNavDestinationSelected(navController) || super.onOptionsItemSelected(item)
}
} }

View File

@@ -17,7 +17,7 @@ class ViewHolderCurrent(listItemView: View) : RecyclerView.ViewHolder(listItemVi
var tempUnit: TextView = listItemView.findViewById(R.id.temp_unit_4) var tempUnit: TextView = listItemView.findViewById(R.id.temp_unit_4)
fun bindData(weather: WeatherDisplay?){ fun bindData(weather: WeatherDisplay?){
locationTV.text = weather?.location locationTV.text = weather?.displayName
conditionTV.text = weather?.description conditionTV.text = weather?.description
weatherIV.loadImage(weather?.iconURL) weatherIV.loadImage(weather?.iconURL)
avgTempTV.text = weather?.averageTemp?.toInt().toString() avgTempTV.text = weather?.averageTemp?.toInt().toString()

View File

@@ -7,28 +7,20 @@ import android.view.ViewGroup
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.observe import androidx.lifecycle.observe
import com.appttude.h_mal.atlas_weather.R import com.appttude.h_mal.atlas_weather.R
import com.appttude.h_mal.atlas_weather.ui.BaseFragment
import com.appttude.h_mal.atlas_weather.utils.displayToast import com.appttude.h_mal.atlas_weather.utils.displayToast
import com.appttude.h_mal.atlas_weather.utils.goBack import com.appttude.h_mal.atlas_weather.utils.goBack
import com.appttude.h_mal.atlas_weather.viewmodel.ApplicationViewModelFactory import com.appttude.h_mal.atlas_weather.viewmodel.ApplicationViewModelFactory
import com.appttude.h_mal.atlas_weather.viewmodel.WorldViewModel import com.appttude.h_mal.atlas_weather.viewmodel.WorldViewModel
import com.appttude.h_mal.monoWeather.ui.BaseFragment
import kotlinx.android.synthetic.main.activity_add_forecast.* import kotlinx.android.synthetic.main.activity_add_forecast.*
import org.kodein.di.KodeinAware import org.kodein.di.KodeinAware
import org.kodein.di.android.x.kodein import org.kodein.di.android.x.kodein
import org.kodein.di.generic.instance import org.kodein.di.generic.instance
class AddLocationFragment : BaseFragment(), KodeinAware { class AddLocationFragment : BaseFragment(R.layout.activity_add_forecast) {
override val kodein by kodein()
private val factory by instance<ApplicationViewModelFactory>()
private val viewModel by viewModels<WorldViewModel> { factory } private val viewModel by getFragmentViewModel<WorldViewModel>()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.activity_add_forecast, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)

View File

@@ -9,12 +9,10 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.observe import androidx.lifecycle.observe
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.appttude.h_mal.atlas_weather.R import com.appttude.h_mal.atlas_weather.R
import com.appttude.h_mal.atlas_weather.ui.world.WorldRecyclerAdapter
import com.appttude.h_mal.atlas_weather.ui.BaseFragment
import com.appttude.h_mal.atlas_weather.ui.world.WorldFragmentDirections
import com.appttude.h_mal.atlas_weather.utils.navigateTo import com.appttude.h_mal.atlas_weather.utils.navigateTo
import com.appttude.h_mal.atlas_weather.viewmodel.ApplicationViewModelFactory import com.appttude.h_mal.atlas_weather.viewmodel.ApplicationViewModelFactory
import com.appttude.h_mal.atlas_weather.viewmodel.WorldViewModel import com.appttude.h_mal.atlas_weather.viewmodel.WorldViewModel
import com.appttude.h_mal.monoWeather.ui.BaseFragment
import kotlinx.android.synthetic.main.fragment_add_location.* import kotlinx.android.synthetic.main.fragment_add_location.*
import org.kodein.di.KodeinAware import org.kodein.di.KodeinAware
import org.kodein.di.android.x.kodein import org.kodein.di.android.x.kodein
@@ -25,17 +23,8 @@ import org.kodein.di.generic.instance
* A simple [Fragment] subclass. * A simple [Fragment] subclass.
* create an instance of this fragment. * create an instance of this fragment.
*/ */
class WorldFragment : BaseFragment(), KodeinAware { class WorldFragment : BaseFragment(R.layout.fragment_add_location) {
override val kodein by kodein() val viewModel by getFragmentViewModel<WorldViewModel>()
private val factory by instance<ApplicationViewModelFactory>()
val viewModel by viewModels<WorldViewModel> { factory }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_add_location, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:startColor="@color/colour_two"
android:centerColor="@color/colour_three"
android:endColor="@color/colour_four"
android:type="linear"
android:angle="45"/>
</shape>

Binary file not shown.

View File

@@ -76,7 +76,7 @@
android:id="@+id/icon_main_4" android:id="@+id/icon_main_4"
android:layout_width="64dp" android:layout_width="64dp"
android:layout_height="64dp" android:layout_height="64dp"
tools:src="@drawable/day_305" /> tools:srcCompat="@drawable/water_drop" />
<LinearLayout <LinearLayout
android:layout_width="0dp" android:layout_width="0dp"

View File

@@ -48,7 +48,9 @@
android:id="@+id/list_icon" android:id="@+id/list_icon"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:src="@drawable/day_305" /> android:adjustViewBounds="true"
tools:layout_width="32dp"
tools:srcCompat="@drawable/water_drop" />
<LinearLayout <LinearLayout
android:layout_width="0dp" android:layout_width="0dp"

View File

@@ -55,4 +55,8 @@
app:argType="com.appttude.h_mal.atlas_weather.model.forecast.WeatherDisplay" /> app:argType="com.appttude.h_mal.atlas_weather.model.forecast.WeatherDisplay" />
</fragment> </fragment>
<fragment
android:id="@+id/settings_fragment"
android:name="com.appttude.h_mal.atlas_weather.ui.settings.SettingsFragment"
android:label="SettingsFragment" />
</navigation> </navigation>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
<color name="colour_one">#E8D0DD</color>
<color name="colour_two">#5F8E7B</color>
<color name="colour_three">#B3C0CA</color>
<color name="colour_four">#8C98AD</color>
<color name="colour_five">#2E3532</color>
</resources>

View File

@@ -3,24 +3,27 @@
<!-- Base application theme. --> <!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. --> <!-- Customize your theme here. -->
<item name="colorPrimary">@android:color/black</item> <item name="colorPrimary">@android:color/transparent</item>
<item name="colorPrimaryDark">@color/colour_four</item> <item name="colorPrimaryDark">@color/colour_four</item>
<item name="colorAccent">@color/colour_one</item> <item name="colorAccent">@color/colour_one</item>
<item name="android:windowBackground">@drawable/gradient</item>
<item name="fontFamily">sans-serif-light</item> <item name="fontFamily">sans-serif-light</item>
<item name="android:textColor">@color/colorAccent</item>
</style> </style>
<style name="AppTheme.NoActionBar"> <style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item> <item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item> <item name="windowNoTitle">true</item>
<item name="android:windowBackground">@drawable/gradient</item>
</style> </style>
<style name="TextAppearance.AppCompat.Widget.ActionBar.Title" parent="@android:style/TextAppearance"> <style name="TextAppearance.AppCompat.Widget.ActionBar.Title" parent="@android:style/TextAppearance">
<item name="android:fontFamily">@font/archeologicaps</item>
<!--<item name="android:textColor">@color/colour_five</item>--> <!--<item name="android:textColor">@color/colour_five</item>-->
</style> </style>
<style name="titlebar" parent="@android:style/TextAppearance"> <style name="titlebar" parent="@android:style/TextAppearance">
<item name="android:fontFamily">@font/archeologicaps</item>
<!--<item name="android:textColor">@color/colour_five</item>--> <!--<item name="android:textColor">@color/colour_five</item>-->
</style> </style>
@@ -42,12 +45,4 @@
<item name="android:textSize">32sp</item> <item name="android:textSize">32sp</item>
</style> </style>
<style name="icon_style__further_deatils"> </resources>
<item name="android:layout_width">64dp</item>
<item name="android:layout_height">64dp</item>
<item name="android:adjustViewBounds">true</item>
<item name="android:layout_gravity">center</item>
<item name="android:tint">@color/colorAccent</item>
</style>
</resources>

View File

@@ -7,6 +7,8 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="com.android.alarm.permission.SET_ALARM" /> <uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<application android:networkSecurityConfig="@xml/network_security_config" /> <application android:networkSecurityConfig="@xml/network_security_config" />

View File

@@ -26,7 +26,7 @@ import kotlin.coroutines.suspendCoroutine
class LocationProviderImpl( class LocationProviderImpl(
private val applicationContext: Context private val applicationContext: Context
) : com.appttude.h_mal.atlas_weather.data.location.LocationProvider, com.appttude.h_mal.atlas_weather.data.location.LocationHelper(applicationContext) { ) : LocationProvider, LocationHelper(applicationContext) {
private var locationManager = private var locationManager =
applicationContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager? applicationContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager?
private val client = FusedLocationProviderClient(applicationContext) private val client = FusedLocationProviderClient(applicationContext)

View File

@@ -26,7 +26,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@android:color/black" android:background="@android:color/black"
android:visibility="gone" android:visibility="gone"
tools:visibility="visible"> tools:visibility="gone">
<ProgressBar <ProgressBar
android:layout_gravity="center" android:layout_gravity="center"
style="?android:attr/progressBarStyle" style="?android:attr/progressBarStyle"

View File

@@ -27,4 +27,12 @@
<item name="android:textColor">#ffffff</item> <item name="android:textColor">#ffffff</item>
<item name="android:textSize">12sp</item> <item name="android:textSize">12sp</item>
</style> </style>
<style name="icon_style__further_deatils">
<item name="android:layout_width">48dp</item>
<item name="android:layout_height">48dp</item>
<item name="android:adjustViewBounds">true</item>
<item name="android:layout_gravity">center</item>
<item name="android:tint">@color/colorAccent</item>
</style>
</resources> </resources>

View File

@@ -2,9 +2,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<application <application
android:name="com.appttude.h_mal.atlas_weather.application.AppClass" android:name="com.appttude.h_mal.atlas_weather.application.AppClass"
android:allowBackup="true" android:allowBackup="true"

View File

@@ -66,12 +66,4 @@
<item name="android:textSize">32sp</item> <item name="android:textSize">32sp</item>
</style> </style>
<style name="icon_style__further_deatils">
<item name="android:layout_width">48dp</item>
<item name="android:layout_height">48dp</item>
<item name="android:adjustViewBounds">true</item>
<item name="android:layout_gravity">center</item>
<item name="android:tint">@color/colorAccent</item>
</style>
</resources> </resources>

View File

@@ -1,2 +0,0 @@
json_key_file("") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one
package_name("") # e.g. com.krausefx.app

View File

@@ -16,23 +16,30 @@
default_platform(:android) default_platform(:android)
platform :android do platform :android do
desc "Runs all the tests"
lane :test do desc "Deploy a new Mono Weather version to the Google Play"
gradle(task: "test") lane :deployMono do
gradle(
task: "clean bundle",
flavor: "MonoWeather",
build_type: "Release",
)
upload_to_play_store(
aab: "app/build/outputs/bundle/monoWeather/app-driver-release.aab",
json_key: "google-play-key.json",
package_name: "h_mal.appttude.com.monoWeather")
end end
desc "Submit a new Beta Build to Crashlytics Beta" desc "Deploy a new Atlas Weather version to the Google Play"
lane :beta do lane :deployMono do
gradle(task: "clean assembleRelease") gradle(
crashlytics task: "clean bundle",
flavor: "AtlasWeather",
# sh "your_script.sh" build_type: "Release",
# You can also use other beta testing services here )
end upload_to_play_store(
aab: "app/build/outputs/bundle/atlasWeather/app-driver-release.aab",
desc "Deploy a new version to the Google Play" json_key: "google-play-key.json",
lane :deploy do package_name: "h_mal.appttude.com.monoWeather")
gradle(task: "clean assembleRelease")
upload_to_play_store
end end
end end