diff --git a/Changelog.md b/Changelog.md index a14d5fadd..dfb3c4d00 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,15 @@ # Most recent version +## v3.1.1 +``` +This version contains a bunch of bug fixes! Here's just a few: +- Creating or opening an event should no longer crash the app +- Fixed sharing games through the game overview +- Prevented scores from dropping below zero when there are too many fouls +``` + +# Legacy versions + ## v3.1.0 ``` Thank you for using the app while some of the kinks in the new version are worked out! @@ -12,8 +22,6 @@ Bug fixes - Fix some more rotation crashes and returning to the app from the background ``` -# Legacy versions - ## v3.0.2 ``` diff --git a/app/build.gradle b/app/build.gradle index 7b8c65e9b..80568badd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,7 +6,7 @@ apply plugin: 'kotlin-android-extensions' def versionMajor = 3 def versionMinor = 1 -def versionPatch = 0 +def versionPatch = 1 android { compileSdkVersion 28 @@ -32,8 +32,7 @@ android { ["BANNER_AD_UNIT_ID", "ca-app-pub-3940256099942544/6300978111"], ["ADMOB_APP_ID", "ca-app-pub-3940256099942544~3347511713"], ["TRANSFER_API_KEY", "API_KEY_GOES_HERE"], - ["MIXPANEL_TOKEN", "API_KEY_GOES_HERE"], - ["BUGSNAG_TOKEN", "API_KEY_GOES_HERE"] + ["MIXPANEL_TOKEN", "API_KEY_GOES_HERE"] ] apiKeys.each { key, value -> @@ -48,9 +47,9 @@ android { def apiKeys = [ ["TRANSFER_SERVER_URL", "API_KEY_GOES_HERE"], ["BANNER_AD_UNIT_ID", "API_KEY_GOES_HERE"], + ["ADMOB_APP_ID", "ca-app-pub-3940256099942544~3347511713"], ["TRANSFER_API_KEY", "API_KEY_GOES_HERE"], - ["MIXPANEL_TOKEN", "API_KEY_GOES_HERE"], - ["BUGSNAG_TOKEN", "API_KEY_GOES_HERE"] + ["MIXPANEL_TOKEN", "API_KEY_GOES_HERE"] ] apiKeys.each { key, value -> @@ -76,16 +75,13 @@ dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.22.2' implementation 'android.arch.lifecycle:extensions:1.1.1' - implementation 'com.nex3z:flow-layout:1.2.2' // https://github.com/nex3z/FlowLayout - implementation 'com.robertlevonyan.view:MaterialChipView:1.2.4' // https://github.com/robertlevonyan/materialChipView implementation 'com.ncapdevi:frag-nav:2.4.0' // https://github.com/ncapdevi/FragNav implementation 'com.github.PhilJay:MPAndroidChart:v3.0.3' // https://github.com/PhilJay/MPAndroidChart - implementation 'com.mixpanel.android:mixpanel-android:5.4.2' // https://github.com/mixpanel/mixpanel-android - implementation 'com.bugsnag:bugsnag-android:4.8.2' // https://github.com/bugsnag/bugsnag-android + implementation 'com.mixpanel.android:mixpanel-android:5.4.4' // https://github.com/mixpanel/mixpanel-android implementation 'com.google.android.gms:play-services-gcm:16.0.0' - implementation 'com.google.android.gms:play-services-ads:17.0.0' + implementation 'com.google.android.gms:play-services-ads:17.1.2' testImplementation 'junit:junit:4.12' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ed517d92b..df4576a59 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -19,16 +19,14 @@ android:networkSecurityConfig="@xml/network_security_config" tools:ignore="GoogleAppIndexingWarning,UnusedAttribute"> + + - - + + + + diff --git a/app/src/main/assets/changelog.txt b/app/src/main/assets/changelog.txt index 85cd94f3c..9deb1efd0 100644 --- a/app/src/main/assets/changelog.txt +++ b/app/src/main/assets/changelog.txt @@ -1,3 +1,9 @@ +v3.1.1 +This version contains a bunch of bug fixes! Here's just a few: +- Creating or opening an event should no longer crash the app +- Fixed sharing games through the game overview +- Prevented scores from dropping below zero when there are too many fouls + v3.1.0 Thank you for using the app while some of the kinks in the new version are worked out! - See an overview of your games by tapping the list icon in the top right, or the overview menu item diff --git a/app/src/main/assets/licenses.txt b/app/src/main/assets/licenses.txt index 92e50def5..abdfaaf54 100644 --- a/app/src/main/assets/licenses.txt +++ b/app/src/main/assets/licenses.txt @@ -21,23 +21,6 @@ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -FlowLayout - -FlowLayout -Copyright 2016 nex3z - -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. - FragNav FragNav Android Fragment Navigation Library @@ -55,24 +38,6 @@ 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. -Material Chip View - -Material Chip View -Copyright 2017 Robert Levonyan -Url: https://github.com/robertlevonyan/materialChipView - -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. - Material design icons Material design icons Copyright 2018 Google diff --git a/app/src/main/java/ca/josephroque/bowlingcompanion/App.kt b/app/src/main/java/ca/josephroque/bowlingcompanion/App.kt index ea7fd13a1..175a31900 100644 --- a/app/src/main/java/ca/josephroque/bowlingcompanion/App.kt +++ b/app/src/main/java/ca/josephroque/bowlingcompanion/App.kt @@ -8,7 +8,6 @@ import android.arch.lifecycle.OnLifecycleEvent import android.content.Context import android.support.v7.preference.PreferenceManager import android.view.inputmethod.InputMethodManager -import com.bugsnag.android.Bugsnag import com.google.android.gms.ads.MobileAds import java.util.concurrent.atomic.AtomicBoolean @@ -47,10 +46,6 @@ class App : Application(), LifecycleObserver { super.onCreate() PreferenceManager.setDefaultValues(this, R.xml.pref_app, false) MobileAds.initialize(this, BuildConfig.ADMOB_APP_ID) - - if (!BuildConfig.DEBUG) { - Bugsnag.init(this) - } } @OnLifecycleEvent(Lifecycle.Event.ON_STOP) diff --git a/app/src/main/java/ca/josephroque/bowlingcompanion/NavigationActivity.kt b/app/src/main/java/ca/josephroque/bowlingcompanion/NavigationActivity.kt index c9ad18c0d..8604967cb 100644 --- a/app/src/main/java/ca/josephroque/bowlingcompanion/NavigationActivity.kt +++ b/app/src/main/java/ca/josephroque/bowlingcompanion/NavigationActivity.kt @@ -10,7 +10,6 @@ import android.support.v4.widget.DrawerLayout import android.view.MenuItem import android.view.View import ca.josephroque.bowlingcompanion.bowlers.BowlerListFragment -import ca.josephroque.bowlingcompanion.common.Android import ca.josephroque.bowlingcompanion.common.FabController import ca.josephroque.bowlingcompanion.common.NavigationDrawerController import ca.josephroque.bowlingcompanion.common.interfaces.IFloatingActionButtonHandler @@ -31,7 +30,6 @@ import ca.josephroque.bowlingcompanion.utils.StartupManager import ca.josephroque.bowlingcompanion.utils.isVisible import com.ncapdevi.fragnav.FragNavController import com.ncapdevi.fragnav.FragNavTransactionOptions -import kotlinx.coroutines.experimental.launch import kotlinx.android.synthetic.main.activity_navigation.ad_view as adView import kotlinx.android.synthetic.main.activity_navigation.bottom_navigation as bottomNavigation import kotlinx.android.synthetic.main.activity_navigation.drawer_layout as drawerLayout @@ -169,6 +167,11 @@ class NavigationActivity : BaseActivity(), } override fun onBackPressed() { + if (fragNavController?.isStateSaved == true) { + super.onBackPressed() + return + } + val fragNavController = fragNavController if (fragNavController != null) { if (currentFragment?.popChildFragment() == true) { @@ -279,7 +282,7 @@ class NavigationActivity : BaseActivity(), // MARK: TransactionListener override fun onFragmentTransaction(fragment: Fragment?, transactionType: FragNavController.TransactionType?) { - handleFragmentChange(fragment) + handleFragmentChange(fragment, transactionType) } override fun onTabTransaction(fragment: Fragment?, index: Int) { @@ -311,7 +314,7 @@ class NavigationActivity : BaseActivity(), // MARK: Private functions - private fun handleFragmentChange(fragment: Fragment?) { + private fun handleFragmentChange(fragment: Fragment?, transactionType: FragNavController.TransactionType? = null) { fragNavController?.let { val showBackButton = it.isRootFragment.not() || BottomTab.fromInt(it.currentStackIndex) == BottomTab.Statistics supportActionBar?.setDisplayHomeAsUpEnabled(showBackButton) @@ -340,7 +343,7 @@ class NavigationActivity : BaseActivity(), refreshCurrentFragment() } - if (fragment is StatisticsProviderListFragment) { + if (fragment is StatisticsProviderListFragment && transactionType != FragNavController.TransactionType.POP) { val statisticsContext = fragNavController?.getStack(BottomTab.toInt(BottomTab.Record))?.peek() as? IStatisticsContext fragment.arguments = StatisticsProviderListFragment.buildArguments(statisticsContext?.statisticsProviders ?: emptyList()) } @@ -378,17 +381,9 @@ class NavigationActivity : BaseActivity(), } bottomNavigation.setOnNavigationItemSelectedListener { - launch(Android) { - fragNavController?.switchTab(BottomTab.fromId(it.itemId).ordinal) - } - + fragNavController?.switchTab(BottomTab.fromId(it.itemId).ordinal) return@setOnNavigationItemSelectedListener true } - - bottomNavigation.setOnNavigationItemReselectedListener { - // FIXME: probably refresh the current fragment, not reset the stack -// fragNavController?.clearStack() - } } private fun setupNavigationDrawer() { diff --git a/app/src/main/java/ca/josephroque/bowlingcompanion/common/FragmentBreadcrumbLogger.kt b/app/src/main/java/ca/josephroque/bowlingcompanion/common/FragmentBreadcrumbLogger.kt deleted file mode 100644 index a538b75f2..000000000 --- a/app/src/main/java/ca/josephroque/bowlingcompanion/common/FragmentBreadcrumbLogger.kt +++ /dev/null @@ -1,51 +0,0 @@ -package ca.josephroque.bowlingcompanion.common - -import android.os.Bundle -import android.support.v4.app.Fragment -import android.support.v4.app.FragmentManager -import ca.josephroque.bowlingcompanion.BuildConfig -import com.bugsnag.android.BreadcrumbType -import com.bugsnag.android.Bugsnag - -/** - * Copyright (C) 2018 Joseph Roque - * - * Add breadcrumb logging to fragments for Bugsnag. - */ -class FragmentBreadcrumbLogger : FragmentManager.FragmentLifecycleCallbacks() { - companion object { - @Suppress("unused") - private const val TAG = "FragmentBreadcrumbLogger" - - private const val FRAG_LIFECYCLE_CALLBACK = "FragmentLifecycleCallback" - } - - // MARK: FragmentLifecycleCallbacks - - override fun onFragmentCreated(fm: FragmentManager, f: Fragment, savedInstanceState: Bundle?) { - leaveLifecycleBreadcrumb(f, "onFragmentCreated()") - } - - override fun onFragmentDestroyed(fm: FragmentManager, f: Fragment) { - leaveLifecycleBreadcrumb(f, "onFragmentDestroyed()") - } - - override fun onFragmentResumed(fm: FragmentManager, f: Fragment) { - leaveLifecycleBreadcrumb(f, "onFragmentResumed()") - } - - override fun onFragmentPaused(fm: FragmentManager, f: Fragment) { - leaveLifecycleBreadcrumb(f, "onFragmentPaused()") - } - - // MARK: Private functions - - private fun leaveLifecycleBreadcrumb(fragment: Fragment, lifecycleCallback: String) { - if (BuildConfig.DEBUG) { return } - val fragmentName = fragment.javaClass.simpleName - - val metadata = HashMap() - metadata[FRAG_LIFECYCLE_CALLBACK] = lifecycleCallback - Bugsnag.leaveBreadcrumb(fragmentName, BreadcrumbType.NAVIGATION, metadata) - } -} diff --git a/app/src/main/java/ca/josephroque/bowlingcompanion/common/activities/BaseActivity.kt b/app/src/main/java/ca/josephroque/bowlingcompanion/common/activities/BaseActivity.kt index a02d437ae..2be0df78e 100644 --- a/app/src/main/java/ca/josephroque/bowlingcompanion/common/activities/BaseActivity.kt +++ b/app/src/main/java/ca/josephroque/bowlingcompanion/common/activities/BaseActivity.kt @@ -6,7 +6,6 @@ import android.view.Menu import android.view.MenuItem import ca.josephroque.bowlingcompanion.BuildConfig import ca.josephroque.bowlingcompanion.R -import ca.josephroque.bowlingcompanion.common.FragmentBreadcrumbLogger import ca.josephroque.bowlingcompanion.settings.SettingsActivity import ca.josephroque.bowlingcompanion.utils.Analytics import ca.josephroque.bowlingcompanion.utils.Email @@ -23,20 +22,8 @@ abstract class BaseActivity : AppCompatActivity() { private const val TAG = "BaseActivity" } - private val fragmentBreadcrumbLogger = FragmentBreadcrumbLogger() - // MARK: Lifecycle functions - override fun onStart() { - super.onStart() - supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentBreadcrumbLogger, true) - } - - override fun onStop() { - super.onStop() - supportFragmentManager.unregisterFragmentLifecycleCallbacks(fragmentBreadcrumbLogger) - } - override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.base_activity, menu) return true diff --git a/app/src/main/java/ca/josephroque/bowlingcompanion/common/adapters/BaseRecyclerViewAdapter.kt b/app/src/main/java/ca/josephroque/bowlingcompanion/common/adapters/BaseRecyclerViewAdapter.kt index a7fe3bfef..41a59129b 100644 --- a/app/src/main/java/ca/josephroque/bowlingcompanion/common/adapters/BaseRecyclerViewAdapter.kt +++ b/app/src/main/java/ca/josephroque/bowlingcompanion/common/adapters/BaseRecyclerViewAdapter.kt @@ -158,7 +158,7 @@ abstract class BaseRecyclerViewAdapter( // MARK:: ViewHolder abstract inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { - abstract fun bind(item: Item, position: Int) + abstract fun bind(item: Item) } // MARK: SwipeCallback @@ -174,8 +174,7 @@ abstract class BaseRecyclerViewAdapter( override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { if (swipeable) { - val position = viewHolder.adapterPosition - delegate?.onItemSwipe(getItemAt(position)) + delegate?.onItemSwipe(getItemAt(viewHolder.adapterPosition)) } } diff --git a/app/src/main/java/ca/josephroque/bowlingcompanion/common/adapters/NameAverageRecyclerViewAdapter.kt b/app/src/main/java/ca/josephroque/bowlingcompanion/common/adapters/NameAverageRecyclerViewAdapter.kt index 2dd0adb21..9010b8ff9 100644 --- a/app/src/main/java/ca/josephroque/bowlingcompanion/common/adapters/NameAverageRecyclerViewAdapter.kt +++ b/app/src/main/java/ca/josephroque/bowlingcompanion/common/adapters/NameAverageRecyclerViewAdapter.kt @@ -74,7 +74,7 @@ class NameAverageRecyclerViewAdapter( } override fun onBindViewHolder(holder: BaseRecyclerViewAdapter.ViewHolder, position: Int) { - holder.bind(getItemAt(position), position) + holder.bind(getItemAt(position)) } // MARK: ViewHolderActive @@ -85,7 +85,7 @@ class NameAverageRecyclerViewAdapter( private val ivIcon: ImageView? = view.findViewById(R.id.iv_name_average) private val checkBox: CheckBox? = view.findViewById(R.id.check_name_average) - override fun bind(item: T, position: Int) { + override fun bind(item: T) { val context = itemView.context tvName?.text = item.name @@ -100,7 +100,7 @@ class NameAverageRecyclerViewAdapter( ivIcon?.visibility = View.VISIBLE checkBox?.visibility = View.GONE - val imageResource = buildImageResource?.invoke(item, position) + val imageResource = buildImageResource?.invoke(item, adapterPosition) imageResource?.let { ivIcon?.setImageResource(it.first) ivIcon?.setColorFilter(it.second) @@ -118,19 +118,19 @@ class NameAverageRecyclerViewAdapter( private val tvDeleted: TextView? = view.findViewById(R.id.tv_deleted) private val tvUndo: TextView? = view.findViewById(R.id.tv_undo) - override fun bind(item: T, position: Int) { + override fun bind(item: T) { val context = itemView.context tvDeleted?.text = String.format( context.resources.getString(R.string.query_delete_item), - getItemAt(position).name + getItemAt(adapterPosition).name ) val deletedItemListener = View.OnClickListener { if (it.id == R.id.tv_undo) { - delegate?.onItemSwipe(getItemAt(position)) + delegate?.onItemSwipe(getItemAt(adapterPosition)) } else { - delegate?.onItemDelete(getItemAt(position)) + delegate?.onItemDelete(getItemAt(adapterPosition)) } } itemView.setOnClickListener(deletedItemListener) diff --git a/app/src/main/java/ca/josephroque/bowlingcompanion/games/Game.kt b/app/src/main/java/ca/josephroque/bowlingcompanion/games/Game.kt index cc7411d0f..90c72a0e3 100644 --- a/app/src/main/java/ca/josephroque/bowlingcompanion/games/Game.kt +++ b/app/src/main/java/ca/josephroque/bowlingcompanion/games/Game.kt @@ -170,7 +170,7 @@ class Game( // Calculate the final score of the game if (!isManual) { - score = totalScore - fouls * Game.FOUL_PENALTY + score = maxOf(totalScore - fouls * Game.FOUL_PENALTY, 0) dirty = false } diff --git a/app/src/main/java/ca/josephroque/bowlingcompanion/games/GameFragment.kt b/app/src/main/java/ca/josephroque/bowlingcompanion/games/GameFragment.kt index e9166517a..59ae39573 100644 --- a/app/src/main/java/ca/josephroque/bowlingcompanion/games/GameFragment.kt +++ b/app/src/main/java/ca/josephroque/bowlingcompanion/games/GameFragment.kt @@ -74,7 +74,6 @@ class GameFragment : BaseFragment(), set(value) { saveCurrentGame(false) field = value - gameHeader.currentGame = gameNumber gameState.currentGameIdx = gameNumber gameState.currentFrame.isAccessed = true render(ballChanged = true, isGameFirstRender = true) @@ -250,6 +249,8 @@ class GameFragment : BaseFragment(), launch(Android) { if (view == null) { return@launch } + // Update current game / frame / ball + gameHeader.currentGame = gameNumber scoreSheet.apply(gameState.currentFrameIdx, gameState.currentBallIdx, gameState.currentGame) // Update up/down pins diff --git a/app/src/main/java/ca/josephroque/bowlingcompanion/games/GameState.kt b/app/src/main/java/ca/josephroque/bowlingcompanion/games/GameState.kt index 0984075bd..2e1d2d867 100644 --- a/app/src/main/java/ca/josephroque/bowlingcompanion/games/GameState.kt +++ b/app/src/main/java/ca/josephroque/bowlingcompanion/games/GameState.kt @@ -338,18 +338,21 @@ class GameState( } fun saveFrame(context: WeakReference, ignoreManualScore: Boolean) { + if (!gamesLoaded) { return } if (!ignoreManualScore && currentGame.isManual) { return } val copy = currentFrame.deepCopy() Saviour.instance.saveFrame(context, currentGame.score, copy) } fun saveGame(context: WeakReference, ignoreManualScore: Boolean) { + if (!gamesLoaded) { return } if (!ignoreManualScore && currentGame.isManual) { return } val copy = currentGame.deepCopy() Saviour.instance.saveGame(context, copy) } fun saveMatchPlay(context: WeakReference) { + if (!gamesLoaded) { return } val copy = currentGame.matchPlay.deepCopy() Saviour.instance.saveMatchPlay(context, copy) } diff --git a/app/src/main/java/ca/josephroque/bowlingcompanion/games/overview/GameOverviewFragment.kt b/app/src/main/java/ca/josephroque/bowlingcompanion/games/overview/GameOverviewFragment.kt index 5b8b5c74b..1fb152955 100644 --- a/app/src/main/java/ca/josephroque/bowlingcompanion/games/overview/GameOverviewFragment.kt +++ b/app/src/main/java/ca/josephroque/bowlingcompanion/games/overview/GameOverviewFragment.kt @@ -20,7 +20,7 @@ import android.support.v7.app.AlertDialog import ca.josephroque.bowlingcompanion.statistics.interfaces.IStatisticsContext import ca.josephroque.bowlingcompanion.statistics.provider.StatisticsProvider import ca.josephroque.bowlingcompanion.utils.Permission -import ca.josephroque.bowlingcompanion.utils.ShareUtils +import ca.josephroque.bowlingcompanion.utils.sharing.ShareUtils /** * Copyright (C) 2018 Joseph Roque @@ -208,15 +208,16 @@ class GameOverviewFragment : ListFragment .setPositiveButton(R.string.okay) { dialog, _ -> if (dialog is AlertDialog) { val selectedItem = ShareOption.fromInt(dialog.listView.checkedItemPosition)!! + externalPermissionsGrantedCallback = { + when (selectedItem) { + ShareOption.Share -> ShareUtils.shareGames(activity, sortedGames) + ShareOption.Save -> ShareUtils.saveGames(activity, sortedGames) + } + externalPermissionsGrantedCallback = null + } when (selectedItem) { ShareOption.Share -> ShareUtils.shareGames(activity, sortedGames) - ShareOption.Save -> { - externalPermissionsGrantedCallback = { - ShareUtils.saveGames(activity, sortedGames) - externalPermissionsGrantedCallback = null - } - ShareUtils.saveGames(activity, sortedGames) - } + ShareOption.Save -> ShareUtils.saveGames(activity, sortedGames) } } dialog.dismiss() diff --git a/app/src/main/java/ca/josephroque/bowlingcompanion/games/overview/GameOverviewRecyclerViewAdapter.kt b/app/src/main/java/ca/josephroque/bowlingcompanion/games/overview/GameOverviewRecyclerViewAdapter.kt index 1765a8ff5..2cc3499ea 100644 --- a/app/src/main/java/ca/josephroque/bowlingcompanion/games/overview/GameOverviewRecyclerViewAdapter.kt +++ b/app/src/main/java/ca/josephroque/bowlingcompanion/games/overview/GameOverviewRecyclerViewAdapter.kt @@ -38,7 +38,7 @@ class GameOverviewRecyclerViewAdapter( } override fun onBindViewHolder(holder: BaseRecyclerViewAdapter.ViewHolder, position: Int) { - holder.bind(getItemAt(position), position) + holder.bind(getItemAt(position)) } // MARK: ViewHolder @@ -48,7 +48,7 @@ class GameOverviewRecyclerViewAdapter( private val scoreSheet: ScoreSheet? = view.findViewById(R.id.score_sheet) private val checkBox: CheckBox? = view.findViewById(R.id.checkbox_share) - override fun bind(item: Game, position: Int) { + override fun bind(item: Game) { val context = itemView.context tvGameNumber?.text = context.resources.getString(R.string.game_number).format(item.ordinal) @@ -65,12 +65,12 @@ class GameOverviewRecyclerViewAdapter( it.apply(-1, -1, item) // Remember scroll position - val (x, y) = scrollOffsets[position] ?: Pair(0, 0) + val (x, y) = scrollOffsets[adapterPosition] ?: Pair(0, 0) it.scrollTo(x, y) it.delegate = object : ScoreSheet.SheetScrollListener { override fun didScroll(x: Int, y: Int) { - scrollOffsets[position] = Pair(x, y) + scrollOffsets[adapterPosition] = Pair(x, y) } } } diff --git a/app/src/main/java/ca/josephroque/bowlingcompanion/leagues/League.kt b/app/src/main/java/ca/josephroque/bowlingcompanion/leagues/League.kt index 2afc05504..26f151a42 100644 --- a/app/src/main/java/ca/josephroque/bowlingcompanion/leagues/League.kt +++ b/app/src/main/java/ca/josephroque/bowlingcompanion/leagues/League.kt @@ -213,6 +213,8 @@ data class League( private fun isLeagueNameValid(name: String): Boolean = REGEX_NAME.matches(name) + private fun nameMatchesPracticeLeague(name: String): Boolean = PRACTICE_LEAGUE_NAME.toLowerCase().equals(name.toLowerCase()) + private fun isLeagueNameUnique(context: Context, name: String, id: Long = -1): Deferred { return async(CommonPool) { val database = DatabaseManager.getReadableDatabase(context).await() @@ -256,10 +258,12 @@ data class League( return async(CommonPool) { val errorTitle = if (isEvent) R.string.issue_saving_event else R.string.issue_saving_league val errorMessage: Int? - if (!isLeagueNameValid(name)) { + if (nameMatchesPracticeLeague(name)) { + errorMessage = R.string.error_league_name_is_practice + } else if (!isLeagueNameValid(name)) { errorMessage = if (isEvent) R.string.error_event_name_invalid else R.string.error_league_name_invalid } else if (!isLeagueNameUnique(context, name, id).await()) { - errorMessage = if (isEvent) R.string.error_event_name_in_use else R.string.error_league_name_in_use + errorMessage = R.string.error_league_name_in_use } else if (name == PRACTICE_LEAGUE_NAME) { errorMessage = R.string.error_cannot_edit_practice_league } else if ( @@ -374,7 +378,7 @@ data class League( * If the new entry is an event, its series is also created at this time * since there is only a single series to an event */ - val (series, seriesError) = league.createNewSeries(context).await() + val (series, seriesError) = league.createNewSeries(context, database).await() if (seriesError != null || (series?.id ?: -1L) == -1L) { throw IllegalStateException("Series was not saved.") } diff --git a/app/src/main/java/ca/josephroque/bowlingcompanion/matchplay/MatchPlaySheet.kt b/app/src/main/java/ca/josephroque/bowlingcompanion/matchplay/MatchPlaySheet.kt index a5407538d..92726de25 100644 --- a/app/src/main/java/ca/josephroque/bowlingcompanion/matchplay/MatchPlaySheet.kt +++ b/app/src/main/java/ca/josephroque/bowlingcompanion/matchplay/MatchPlaySheet.kt @@ -9,7 +9,6 @@ import android.support.v4.app.DialogFragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.FrameLayout import ca.josephroque.bowlingcompanion.R import ca.josephroque.bowlingcompanion.common.fragments.BaseBottomSheetDialogFragment import ca.josephroque.bowlingcompanion.games.Game @@ -65,7 +64,7 @@ class MatchPlaySheet : BaseBottomSheetDialogFragment() { val dialog = super.onCreateDialog(savedInstanceState) dialog.setOnShowListener { if (it is BottomSheetDialog) { - val bottomSheet = it.findViewById(R.id.design_bottom_sheet) + val bottomSheet = it.findViewById(android.support.design.R.id.design_bottom_sheet) val bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet) bottomSheetBehavior.setBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() { diff --git a/app/src/main/java/ca/josephroque/bowlingcompanion/series/SeriesRecyclerViewAdapter.kt b/app/src/main/java/ca/josephroque/bowlingcompanion/series/SeriesRecyclerViewAdapter.kt index 7c38d1d9f..1f00b6cb8 100644 --- a/app/src/main/java/ca/josephroque/bowlingcompanion/series/SeriesRecyclerViewAdapter.kt +++ b/app/src/main/java/ca/josephroque/bowlingcompanion/series/SeriesRecyclerViewAdapter.kt @@ -1,5 +1,7 @@ package ca.josephroque.bowlingcompanion.series +import android.support.design.chip.Chip +import android.support.design.chip.ChipGroup import android.support.v4.content.ContextCompat import android.support.v7.preference.PreferenceManager import android.support.v7.widget.RecyclerView @@ -12,7 +14,6 @@ import ca.josephroque.bowlingcompanion.common.adapters.BaseRecyclerViewAdapter import ca.josephroque.bowlingcompanion.matchplay.MatchPlayResult import ca.josephroque.bowlingcompanion.leagues.League import ca.josephroque.bowlingcompanion.settings.Settings -import com.nex3z.flowlayout.FlowLayout /** * Copyright (C) 2018 Joseph Roque @@ -98,7 +99,7 @@ class SeriesRecyclerViewAdapter( } override fun onBindViewHolder(holder: BaseRecyclerViewAdapter.ViewHolder, position: Int) { - holder.bind(getItemAt(position), position) + holder.bind(getItemAt(position)) } // MARK: SeriesRecyclerViewAdapter @@ -125,7 +126,7 @@ class SeriesRecyclerViewAdapter( private val tvDate: TextView? = view.findViewById(R.id.tv_date) private val tvTotal: TextView? = view.findViewById(R.id.tv_total) - override fun bind(item: Series, position: Int) { + override fun bind(item: Series) { val context = itemView.context val seriesTotal = item.scores.sum() @@ -149,9 +150,9 @@ class SeriesRecyclerViewAdapter( private val tvDate: TextView? = view.findViewById(R.id.tv_date) private val tvTotal: TextView? = view.findViewById(R.id.tv_total) - private val flowScores: FlowLayout? = view.findViewById(R.id.flow_scores) + private val chipGroupScores: ChipGroup? = view.findViewById(R.id.cg_scores) - override fun bind(item: Series, position: Int) { + override fun bind(item: Series) { val context = itemView.context val seriesTotal = item.scores.sum() @@ -168,32 +169,47 @@ class SeriesRecyclerViewAdapter( val shouldShowMatchPlayResult = Settings.BooleanSetting.ShowMatchResults.getValue(preferences) val shouldHighlightMatchPlayResult = Settings.BooleanSetting.HighlightMatchResults.getValue(preferences) - flowScores?.removeAllViews() + chipGroupScores?.removeAllViews() if (shouldShowMatchPlayResult) { for (i in 0 until item.scores.size) { - val id = View.generateViewId() - val scoreView = SeriesScoreView(context) + val viewId = View.generateViewId() + val score = item.scores[i] val matchPlayResult = MatchPlayResult.fromInt(item.matchPlay[i].toInt())!! - scoreView.id = id - scoreView.isFocusable = false - scoreView.isClickable = false - scoreView.score = item.scores[i] - scoreView.matchPlay = matchPlayResult - - scoreView.scoreTextColor = if (shouldHighlightGame(item.scores[i])) { - ContextCompat.getColor(context, R.color.gameHighlight) - } else { - ContextCompat.getColor(context, R.color.primaryBlackText) - } - when { - !shouldHighlightMatchPlayResult || matchPlayResult == MatchPlayResult.NONE -> scoreView.matchPlayTextColor = ContextCompat.getColor(context, R.color.primaryBlackText) - matchPlayResult == MatchPlayResult.WON -> scoreView.matchPlayTextColor = ContextCompat.getColor(context, R.color.matchPlayWin) - matchPlayResult == MatchPlayResult.LOST -> scoreView.matchPlayTextColor = ContextCompat.getColor(context, R.color.matchPlayLoss) - matchPlayResult == MatchPlayResult.TIED -> scoreView.matchPlayTextColor = ContextCompat.getColor(context, R.color.matchPlayTie) + val chipIconResource: Int? + val chipIconTintResource: Int? + when (matchPlayResult) { + MatchPlayResult.WON -> { + chipIconResource = R.drawable.ic_match_play_won_chip + chipIconTintResource = if (shouldHighlightMatchPlayResult) R.color.matchPlayWin else R.color.primaryBlackText + } + MatchPlayResult.LOST -> { + chipIconResource = R.drawable.ic_match_play_lost_chip + chipIconTintResource = if (shouldHighlightMatchPlayResult) R.color.matchPlayLoss else R.color.primaryBlackText + } + MatchPlayResult.TIED -> { + chipIconResource = R.drawable.ic_match_play_tied_chip + chipIconTintResource = if (shouldHighlightMatchPlayResult) R.color.matchPlayTie else R.color.primaryBlackText + } + MatchPlayResult.NONE -> { + chipIconResource = null + chipIconTintResource = null + } + } + val chipTextColorResource = if (shouldHighlightGame(score)) R.color.gameHighlight else R.color.primaryBlackText + + val chip = Chip(context).apply { + id = viewId + isFocusable = false + isClickable = false + text = score.toString() + setTextColor(ContextCompat.getColor(context, chipTextColorResource)) + chipIconResource?.let { setChipIconResource(it) } + chipIconTintResource?.let { setChipIconTintResource(it) } + setChipBackgroundColorResource(R.color.colorListContrast) } - flowScores?.addView(scoreView) + chipGroupScores?.addView(chip) } } @@ -208,19 +224,19 @@ class SeriesRecyclerViewAdapter( private val tvDeleted: TextView? = view.findViewById(R.id.tv_deleted) private val tvUndo: TextView? = view.findViewById(R.id.tv_undo) - override fun bind(item: Series, position: Int) { + override fun bind(item: Series) { val context = itemView.context tvDeleted?.text = String.format( context.resources.getString(R.string.query_delete_item), - getItemAt(position).prettyDate + getItemAt(adapterPosition).prettyDate ) val deletedItemListener = View.OnClickListener { if (it.id == R.id.tv_undo) { - delegate?.onItemSwipe(getItemAt(position)) + delegate?.onItemSwipe(getItemAt(adapterPosition)) } else { - delegate?.onItemDelete(getItemAt(position)) + delegate?.onItemDelete(getItemAt(adapterPosition)) } } itemView.setOnClickListener(deletedItemListener) diff --git a/app/src/main/java/ca/josephroque/bowlingcompanion/series/SeriesScoreView.kt b/app/src/main/java/ca/josephroque/bowlingcompanion/series/SeriesScoreView.kt deleted file mode 100644 index c2a7c3746..000000000 --- a/app/src/main/java/ca/josephroque/bowlingcompanion/series/SeriesScoreView.kt +++ /dev/null @@ -1,97 +0,0 @@ -package ca.josephroque.bowlingcompanion.series - -import android.content.Context -import android.graphics.Color -import android.os.Bundle -import android.os.Parcelable -import android.util.AttributeSet -import android.view.LayoutInflater -import android.view.View -import android.widget.LinearLayout -import ca.josephroque.bowlingcompanion.R -import ca.josephroque.bowlingcompanion.matchplay.MatchPlayResult -import kotlinx.android.synthetic.main.view_series_score.view.tv_score as tvScore -import kotlinx.android.synthetic.main.view_series_score.view.tv_match_play as tvMatchPlay - -/** - * Copyright (C) 2018 Joseph Roque - * - * Display a score and its match play result in unison. - */ -class SeriesScoreView : LinearLayout { - - companion object { - @Suppress("unused") - private const val TAG = "SeriesScoreView" - - private const val SUPER_STATE = "${TAG}_super_state" - private const val SCORE = "${TAG}_score" - private const val MATCH_PLAY = "${TAG}_match_play" - private const val SCORE_TEXT_COLOR = "${TAG}_score_text_color" - private const val MATCH_PLAY_TEXT_COLOR = "${TAG}_match_play_text_color" - } - - // MARK: Constructors - - constructor(context: Context) : this(context, null) - constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) - constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { - orientation = LinearLayout.VERTICAL - LayoutInflater.from(context).inflate(R.layout.view_series_score, this, true) - } - - // MARK: Lifecycle functions - - override fun onSaveInstanceState(): Parcelable { - return Bundle().apply { - putParcelable(SUPER_STATE, super.onSaveInstanceState()) - putInt(SCORE, score) - putInt(SCORE_TEXT_COLOR, scoreTextColor) - putInt(MATCH_PLAY, matchPlay.ordinal) - putInt(MATCH_PLAY_TEXT_COLOR, matchPlayTextColor) - } - } - - override fun onRestoreInstanceState(state: Parcelable?) { - var superState: Parcelable? = null - if (state is Bundle) { - score = state.getInt(SCORE) - scoreTextColor = state.getInt(SCORE_TEXT_COLOR) - matchPlay = MatchPlayResult.fromInt(state.getInt(MATCH_PLAY))!! - matchPlayTextColor = state.getInt(MATCH_PLAY_TEXT_COLOR) - superState = state.getParcelable(SUPER_STATE) - } - - super.onRestoreInstanceState(superState) - } - - var score: Int = 0 - set(value) { - tvScore.text = value.toString() - field = value - } - - var matchPlay: MatchPlayResult = MatchPlayResult.NONE - set(value) { - when (value) { - MatchPlayResult.NONE -> tvMatchPlay.text = null - MatchPlayResult.WON -> tvMatchPlay.text = context.getString(R.string.match_play_won_short) - MatchPlayResult.LOST -> tvMatchPlay.text = context.getString(R.string.match_play_lost_short) - MatchPlayResult.TIED -> tvMatchPlay.text = context.getString(R.string.match_play_tied_short) - } - tvMatchPlay.visibility = if (value == MatchPlayResult.NONE) View.GONE else View.VISIBLE - field = value - } - - var scoreTextColor: Int = Color.BLACK - set(value) { - tvScore.setTextColor(value) - field = value - } - - var matchPlayTextColor: Int = Color.BLACK - set(value) { - tvMatchPlay.setTextColor(value) - field = value - } -} diff --git a/app/src/main/java/ca/josephroque/bowlingcompanion/statistics/list/StatisticsRecyclerViewAdapter.kt b/app/src/main/java/ca/josephroque/bowlingcompanion/statistics/list/StatisticsRecyclerViewAdapter.kt index 87991f37e..cae83c90c 100644 --- a/app/src/main/java/ca/josephroque/bowlingcompanion/statistics/list/StatisticsRecyclerViewAdapter.kt +++ b/app/src/main/java/ca/josephroque/bowlingcompanion/statistics/list/StatisticsRecyclerViewAdapter.kt @@ -59,7 +59,7 @@ class StatisticsRecyclerViewAdapter( } override fun onBindViewHolder(holder: ViewHolder, position: Int) { - holder.bind(getItemAt(position), position) + holder.bind(getItemAt(position)) } // MARK: HeaderViewHolder @@ -67,7 +67,7 @@ class StatisticsRecyclerViewAdapter( inner class HeaderViewHolder(view: View) : BaseRecyclerViewAdapter.ViewHolder(view) { private val tvTitle: TextView? = view.findViewById(R.id.tv_title) - override fun bind(item: StatisticListItem, position: Int) { + override fun bind(item: StatisticListItem) { val context = itemView.context val header = item as StatisticsCategory @@ -82,7 +82,7 @@ class StatisticsRecyclerViewAdapter( private val tvValue: TextView? = view.findViewById(R.id.tv_value) private val tvSubtitle: TextView? = view.findViewById(R.id.tv_subtitle) - override fun bind(item: StatisticListItem, position: Int) { + override fun bind(item: StatisticListItem) { val context = itemView.context val statistic = item as Statistic diff --git a/app/src/main/java/ca/josephroque/bowlingcompanion/statistics/provider/StatisticsProviderRecyclerViewAdapter.kt b/app/src/main/java/ca/josephroque/bowlingcompanion/statistics/provider/StatisticsProviderRecyclerViewAdapter.kt index 67eac8b9f..4b4857833 100644 --- a/app/src/main/java/ca/josephroque/bowlingcompanion/statistics/provider/StatisticsProviderRecyclerViewAdapter.kt +++ b/app/src/main/java/ca/josephroque/bowlingcompanion/statistics/provider/StatisticsProviderRecyclerViewAdapter.kt @@ -26,9 +26,7 @@ class StatisticsProviderRecyclerViewAdapter( // MARK: BaseRecyclerViewAdapter - override fun getItemViewType(position: Int): Int { - return 0 - } + override fun getItemViewType(position: Int) = 0 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseRecyclerViewAdapter.ViewHolder { return ViewHolderName( @@ -39,7 +37,7 @@ class StatisticsProviderRecyclerViewAdapter( } override fun onBindViewHolder(holder: BaseRecyclerViewAdapter.ViewHolder, position: Int) { - holder.bind(getItemAt(position), position) + holder.bind(getItemAt(position)) } // MARK: ViewHolderName @@ -48,7 +46,7 @@ class StatisticsProviderRecyclerViewAdapter( private val tvName: TextView? = view.findViewById(R.id.tv_name) private val tvType: TextView? = view.findViewById(R.id.tv_type) - override fun bind(item: StatisticsProvider, position: Int) { + override fun bind(item: StatisticsProvider) { tvName?.text = item.name tvType?.setText(item.typeName) itemView.setOnClickListener(this@StatisticsProviderRecyclerViewAdapter) diff --git a/app/src/main/java/ca/josephroque/bowlingcompanion/teams/list/TeamRecyclerViewAdapter.kt b/app/src/main/java/ca/josephroque/bowlingcompanion/teams/list/TeamRecyclerViewAdapter.kt index 6950b609b..792480a05 100644 --- a/app/src/main/java/ca/josephroque/bowlingcompanion/teams/list/TeamRecyclerViewAdapter.kt +++ b/app/src/main/java/ca/josephroque/bowlingcompanion/teams/list/TeamRecyclerViewAdapter.kt @@ -1,5 +1,7 @@ package ca.josephroque.bowlingcompanion.teams.list +import android.support.design.chip.Chip +import android.support.design.chip.ChipGroup import android.support.v4.content.ContextCompat import android.support.v7.widget.RecyclerView import android.view.LayoutInflater @@ -9,8 +11,6 @@ import android.widget.TextView import ca.josephroque.bowlingcompanion.R import ca.josephroque.bowlingcompanion.common.adapters.BaseRecyclerViewAdapter import ca.josephroque.bowlingcompanion.teams.Team -import com.nex3z.flowlayout.FlowLayout -import com.robertlevonyan.views.chip.Chip /** * Copyright (C) 2018 Joseph Roque @@ -61,30 +61,30 @@ class TeamRecyclerViewAdapter( } override fun onBindViewHolder(holder: BaseRecyclerViewAdapter.ViewHolder, position: Int) { - holder.bind(getItemAt(position), position) + holder.bind(getItemAt(position)) } // MARK: ViewHolderActive inner class ViewHolderActive(view: View) : BaseRecyclerViewAdapter.ViewHolder(view) { private val tvName: TextView? = view.findViewById(R.id.tv_name) - private val flowMembers: FlowLayout? = view.findViewById(R.id.flow_members) + private val chipGroupMembers: ChipGroup? = view.findViewById(R.id.cg_members) - override fun bind(item: Team, position: Int) { + override fun bind(item: Team) { val context = itemView.context tvName?.text = item.name - flowMembers?.removeAllViews() + chipGroupMembers?.removeAllViews() item.members.forEach { val viewId = View.generateViewId() Chip(context).apply { id = viewId isFocusable = false isClickable = false - chipText = it.bowlerName - changeBackgroundColor(ContextCompat.getColor(context, R.color.colorPrimary)) - textColor = ContextCompat.getColor(context, R.color.primaryWhiteText) - flowMembers?.addView(this) + text = it.bowlerName + setChipBackgroundColorResource(R.color.colorPrimary) + setTextColor(ContextCompat.getColor(context, R.color.primaryWhiteText)) + chipGroupMembers?.addView(this) } } @@ -99,19 +99,19 @@ class TeamRecyclerViewAdapter( private val tvDeleted: TextView? = view.findViewById(R.id.tv_deleted) private val tvUndo: TextView? = view.findViewById(R.id.tv_undo) - override fun bind(item: Team, position: Int) { + override fun bind(item: Team) { val context = itemView.context tvDeleted?.text = String.format( context.resources.getString(R.string.query_delete_item), - getItemAt(position).name + getItemAt(adapterPosition).name ) val deletedItemListener = View.OnClickListener { if (it.id == R.id.tv_undo) { - delegate?.onItemSwipe(getItemAt(position)) + delegate?.onItemSwipe(getItemAt(adapterPosition)) } else { - delegate?.onItemDelete(getItemAt(position)) + delegate?.onItemDelete(getItemAt(adapterPosition)) } } itemView.setOnClickListener(deletedItemListener) diff --git a/app/src/main/java/ca/josephroque/bowlingcompanion/teams/teammember/TeamMembersRecyclerViewAdapter.kt b/app/src/main/java/ca/josephroque/bowlingcompanion/teams/teammember/TeamMembersRecyclerViewAdapter.kt index e9ef72b8b..6aaa9a0d4 100644 --- a/app/src/main/java/ca/josephroque/bowlingcompanion/teams/teammember/TeamMembersRecyclerViewAdapter.kt +++ b/app/src/main/java/ca/josephroque/bowlingcompanion/teams/teammember/TeamMembersRecyclerViewAdapter.kt @@ -56,7 +56,7 @@ class TeamMembersRecyclerViewAdapter( } override fun onBindViewHolder(holder: BaseRecyclerViewAdapter.ViewHolder, position: Int) { - holder.bind(getItemAt(position), position) + holder.bind(getItemAt(position)) } override fun buildItemTouchHelper(): ItemTouchHelper.Callback { @@ -71,7 +71,7 @@ class TeamMembersRecyclerViewAdapter( private val tvSeriesName: TextView = view.findViewById(R.id.tv_team_member_series) private val ivIcon: ImageView = view.findViewById(R.id.iv_team_member_icon) - override fun bind(item: TeamMember, position: Int) { + override fun bind(item: TeamMember) { val context = itemView.context ivIcon.setImageResource(R.drawable.ic_menu) ivIcon.setColorFilter(ContextCompat.getColor(context, R.color.primaryBlackIcon)) diff --git a/app/src/main/java/ca/josephroque/bowlingcompanion/utils/StartupManager.kt b/app/src/main/java/ca/josephroque/bowlingcompanion/utils/StartupManager.kt index f2f973e26..593e51008 100644 --- a/app/src/main/java/ca/josephroque/bowlingcompanion/utils/StartupManager.kt +++ b/app/src/main/java/ca/josephroque/bowlingcompanion/utils/StartupManager.kt @@ -19,7 +19,7 @@ object StartupManager { fun start(context: Context) { val appVersion = getAppVersion(context) val isNewVersion = BuildConfig.VERSION_CODE != appVersion - setIsFirstLaunch(context, appVersion != -1) + setIsFirstLaunch(context, appVersion == -1) setAppVersion(context, BuildConfig.VERSION_CODE) val isFirstLaunch = isFirstLaunch(context) @@ -31,8 +31,7 @@ object StartupManager { } fun isFirstLaunch(context: Context): Boolean { - val preferences = PreferenceManager.getDefaultSharedPreferences(context) - return preferences.getBoolean(IS_FIRST_LAUNCH, false) + return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(IS_FIRST_LAUNCH, false) } fun getAppVersion(context: Context): Int { diff --git a/app/src/main/java/ca/josephroque/bowlingcompanion/utils/sharing/GameOverviewBitmapFileProvider.kt b/app/src/main/java/ca/josephroque/bowlingcompanion/utils/sharing/GameOverviewBitmapFileProvider.kt new file mode 100644 index 000000000..01bdaf447 --- /dev/null +++ b/app/src/main/java/ca/josephroque/bowlingcompanion/utils/sharing/GameOverviewBitmapFileProvider.kt @@ -0,0 +1,10 @@ +package ca.josephroque.bowlingcompanion.utils.sharing + +import android.support.v4.content.FileProvider + +/** + * Copyright (C) 2019 Joseph Roque + * + * Provider for Game Overview bitmaps. + */ +class GameOverviewBitmapFileProvider : FileProvider() diff --git a/app/src/main/java/ca/josephroque/bowlingcompanion/utils/ShareUtils.kt b/app/src/main/java/ca/josephroque/bowlingcompanion/utils/sharing/ShareUtils.kt similarity index 78% rename from app/src/main/java/ca/josephroque/bowlingcompanion/utils/ShareUtils.kt rename to app/src/main/java/ca/josephroque/bowlingcompanion/utils/sharing/ShareUtils.kt index ae1b1668a..2e1ef8dd6 100644 --- a/app/src/main/java/ca/josephroque/bowlingcompanion/utils/ShareUtils.kt +++ b/app/src/main/java/ca/josephroque/bowlingcompanion/utils/sharing/ShareUtils.kt @@ -1,4 +1,4 @@ -package ca.josephroque.bowlingcompanion.utils +package ca.josephroque.bowlingcompanion.utils.sharing import android.app.Activity import android.graphics.Bitmap @@ -15,13 +15,18 @@ import kotlinx.coroutines.experimental.launch import android.content.Intent import android.graphics.Canvas import android.graphics.Paint -import android.net.Uri import android.util.Log import android.view.View import ca.josephroque.bowlingcompanion.common.Android import ca.josephroque.bowlingcompanion.games.Game import ca.josephroque.bowlingcompanion.games.views.GameNumberView import ca.josephroque.bowlingcompanion.games.views.ScoreSheet +import ca.josephroque.bowlingcompanion.utils.Analytics +import ca.josephroque.bowlingcompanion.utils.BCError +import ca.josephroque.bowlingcompanion.utils.Permission +import ca.josephroque.bowlingcompanion.utils.toBitmap +import android.support.v4.content.FileProvider +import ca.josephroque.bowlingcompanion.BuildConfig /** * Copyright (C) 2018 Joseph Roque @@ -37,21 +42,30 @@ object ShareUtils { private const val interGameBorderWidth = 4 fun shareGames(activity: Activity, games: List) { - launch(CommonPool) { - val bitmap = buildBitmap(activity, games) - val destination = saveBitmap(activity, games.size, bitmap) - bitmap.recycle() + if (Permission.WriteExternalStorage.requestPermission(activity)) { + launch(CommonPool) { + val bitmap = buildBitmap(activity, games) + val destination = saveBitmap(activity, games.size, bitmap) + bitmap.recycle() - if (destination == null) { return@launch } + if (destination == null) { + return@launch + } - val shareIntent = Intent(Intent.ACTION_SEND) - shareIntent.type = "image/$exportFileType" - shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(destination)) + val providedImage = FileProvider.getUriForFile(activity, + "${BuildConfig.APPLICATION_ID}.utils.sharing.GameOverviewBitmapFileProvider", + destination) - launch(Android) { - activity.startActivity(Intent.createChooser(shareIntent, activity.resources.getString(R.string.share_image))) + val shareIntent = Intent(Intent.ACTION_SEND) + shareIntent.type = "image/$exportFileType" + shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + shareIntent.putExtra(Intent.EXTRA_STREAM, providedImage) - Analytics.trackShareImage(games.size) + launch(Android) { + activity.startActivity(Intent.createChooser(shareIntent, activity.resources.getString(R.string.share_image))) + + Analytics.trackShareImage(games.size) + } } } } diff --git a/app/src/main/res/drawable-hdpi/ic_match_play_lost_chip.png b/app/src/main/res/drawable-hdpi/ic_match_play_lost_chip.png new file mode 100644 index 000000000..c98cae3b0 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_match_play_lost_chip.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_match_play_tied_chip.png b/app/src/main/res/drawable-hdpi/ic_match_play_tied_chip.png new file mode 100644 index 000000000..efbd11596 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_match_play_tied_chip.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_match_play_won_chip.png b/app/src/main/res/drawable-hdpi/ic_match_play_won_chip.png new file mode 100644 index 000000000..c482cea75 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_match_play_won_chip.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_match_play_lost_chip.png b/app/src/main/res/drawable-mdpi/ic_match_play_lost_chip.png new file mode 100644 index 000000000..60ece0386 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_match_play_lost_chip.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_match_play_tied_chip.png b/app/src/main/res/drawable-mdpi/ic_match_play_tied_chip.png new file mode 100644 index 000000000..7fce4c66e Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_match_play_tied_chip.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_match_play_won_chip.png b/app/src/main/res/drawable-mdpi/ic_match_play_won_chip.png new file mode 100644 index 000000000..aba6ae33c Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_match_play_won_chip.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_match_play_lost_chip.png b/app/src/main/res/drawable-xhdpi/ic_match_play_lost_chip.png new file mode 100644 index 000000000..b801b4415 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_match_play_lost_chip.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_match_play_tied_chip.png b/app/src/main/res/drawable-xhdpi/ic_match_play_tied_chip.png new file mode 100644 index 000000000..70deca444 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_match_play_tied_chip.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_match_play_won_chip.png b/app/src/main/res/drawable-xhdpi/ic_match_play_won_chip.png new file mode 100644 index 000000000..d54f5a410 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_match_play_won_chip.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_match_play_lost_chip.png b/app/src/main/res/drawable-xxhdpi/ic_match_play_lost_chip.png new file mode 100644 index 000000000..0b1492e8a Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_match_play_lost_chip.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_match_play_tied_chip.png b/app/src/main/res/drawable-xxhdpi/ic_match_play_tied_chip.png new file mode 100644 index 000000000..4edb2c8d1 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_match_play_tied_chip.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_match_play_won_chip.png b/app/src/main/res/drawable-xxhdpi/ic_match_play_won_chip.png new file mode 100644 index 000000000..0f9068395 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_match_play_won_chip.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_match_play_lost_chip.png b/app/src/main/res/drawable-xxxhdpi/ic_match_play_lost_chip.png new file mode 100644 index 000000000..07f6df332 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_match_play_lost_chip.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_match_play_tied_chip.png b/app/src/main/res/drawable-xxxhdpi/ic_match_play_tied_chip.png new file mode 100644 index 000000000..e7d68c9b9 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_match_play_tied_chip.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_match_play_won_chip.png b/app/src/main/res/drawable-xxxhdpi/ic_match_play_won_chip.png new file mode 100644 index 000000000..b0371133f Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_match_play_won_chip.png differ diff --git a/app/src/main/res/layout/fragment_game.xml b/app/src/main/res/layout/fragment_game.xml index 219f00a04..d291a5225 100644 --- a/app/src/main/res/layout/fragment_game.xml +++ b/app/src/main/res/layout/fragment_game.xml @@ -42,6 +42,7 @@ android:layout_width="0dp" android:layout_height="0dp" android:gravity="center" + android:textAlignment="center" android:textColor="@color/primaryWhiteText" app:layout_constraintTop_toBottomOf="@id/game_header" app:layout_constraintBottom_toTopOf="@id/game_footer" diff --git a/app/src/main/res/layout/list_item_series_expanded.xml b/app/src/main/res/layout/list_item_series_expanded.xml index 7dcc72db5..8ba65c85d 100644 --- a/app/src/main/res/layout/list_item_series_expanded.xml +++ b/app/src/main/res/layout/list_item_series_expanded.xml @@ -1,6 +1,5 @@ - - + app:chipSpacing="@dimen/list_item_margin_small" /> diff --git a/app/src/main/res/layout/list_item_team.xml b/app/src/main/res/layout/list_item_team.xml index eb5abcf46..8f96f6330 100644 --- a/app/src/main/res/layout/list_item_team.xml +++ b/app/src/main/res/layout/list_item_team.xml @@ -17,12 +17,11 @@ - + app:chipSpacing="@dimen/list_item_margin" /> diff --git a/app/src/main/res/layout/view_series_score.xml b/app/src/main/res/layout/view_series_score.xml deleted file mode 100644 index b6a4976d8..000000000 --- a/app/src/main/res/layout/view_series_score.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 6b0a1a0f3..78bb677a1 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -12,6 +12,7 @@ #F5F5F5 #EBEBEB + #E1E1E1 #33000000 #EE5253 diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 8f7c85c31..4d4f9165b 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -30,6 +30,7 @@ 16dp + 8dp 48dp 40dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index da5a35a35..ba891f72d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -164,11 +164,12 @@ Issue saving league! Unable to delete league! You can only use letters, spaces, and the following characters: \'!@#$%^&*()_+:"?/~-. - That name has already been used. You must choose another. + That name has already been used for a league or event. You must choose another. You can only use numbers in your additional games and pinfall! Your additional pinfall and games cannot equal an average greater than 450. Your game highlight must be between 0 and 450, and series highlight cannot be greater than the maximum total series. Leagues and events can be between 1 and 20 games. + You can\'t use the name \'Practice\'. Choose another name or try using the practice league to record your games. An error occurred and the league could not be saved. @@ -176,7 +177,6 @@ Issue saving event! You can only use letters, spaces, and the following characters: \'!@#$%^&*()_+:"?/~-. - That name has already been used. You must choose another. Series diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 8a6efdc99..bbade9916 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -3,7 +3,7 @@ -