├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── app ├── build.gradle ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── chess │ │ │ └── clock │ │ │ ├── ClockApplication.java │ │ │ ├── activities │ │ │ ├── AppSettingsActivity.java │ │ │ ├── BaseActivity.java │ │ │ ├── ClockSoundManager.java │ │ │ ├── ClockTimersActivity.java │ │ │ └── TimerSettingsActivity.java │ │ │ ├── adapters │ │ │ ├── ThemesAdapter.java │ │ │ ├── TimeRowMoveCallback.java │ │ │ └── TimesAdapter.java │ │ │ ├── dialog │ │ │ ├── AdjustTimeDialogFragment.java │ │ │ ├── EditStageDialogFragment.java │ │ │ ├── EditTimeIncrementDialogFragment.java │ │ │ └── FullScreenDialogFragment.java │ │ │ ├── engine │ │ │ ├── ClockPlayer.java │ │ │ ├── CountDownTimer.java │ │ │ ├── Stage.java │ │ │ ├── StageManager.java │ │ │ ├── TimeControl.java │ │ │ ├── TimeControlDefaults.java │ │ │ ├── TimeControlManager.java │ │ │ ├── TimeControlParser.java │ │ │ ├── TimeControlWrapper.java │ │ │ └── TimeIncrement.java │ │ │ ├── entities │ │ │ ├── AppTheme.java │ │ │ └── ClockTime.java │ │ │ ├── fragments │ │ │ ├── BaseFragment.java │ │ │ ├── TimeControlFragment.java │ │ │ └── TimeSettingsFragment.java │ │ │ ├── manager │ │ │ ├── ChessClockManager.java │ │ │ └── ChessClockManagerImpl.java │ │ │ ├── statics │ │ │ ├── AppConstants.java │ │ │ ├── AppData.java │ │ │ └── StaticData.java │ │ │ ├── util │ │ │ ├── Args.java │ │ │ ├── AutoNameFormatter.java │ │ │ └── ClockUtils.java │ │ │ └── views │ │ │ ├── ClockButton.java │ │ │ ├── ClockMenu.java │ │ │ ├── StageRowView.java │ │ │ ├── StyledButton.java │ │ │ └── ViewUtils.java │ └── res │ │ ├── anim │ │ ├── left_to_right_full.xml │ │ ├── left_to_right_in.xml │ │ ├── right_to_left_full.xml │ │ └── right_to_left_out.xml │ │ ├── drawable-v21 │ │ └── bg_green_pressed.xml │ │ ├── drawable │ │ ├── add_time_button_bg.xml │ │ ├── bg_btn_clock_running.xml │ │ ├── bg_green_pressed.xml │ │ ├── bg_popup.xml │ │ ├── bg_white_50_rounded.xml │ │ ├── button_bg_selector_green.xml │ │ ├── ic_arrow_right_24.xml │ │ ├── ic_back_arrow_white.xml │ │ ├── ic_check_box.xml │ │ ├── ic_check_box_frame.xml │ │ ├── ic_checkmark.xml │ │ ├── ic_checkmark_white.xml │ │ ├── ic_delete.xml │ │ ├── ic_edit.xml │ │ ├── ic_edit_pencil.xml │ │ ├── ic_edit_time.xml │ │ ├── ic_edit_time_small.xml │ │ ├── ic_fullscreen.xml │ │ ├── ic_fullscreen_exit.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_launcher_foreground.xml │ │ ├── ic_logo.xml │ │ ├── ic_options.xml │ │ ├── ic_pause.xml │ │ ├── ic_play.xml │ │ ├── ic_plus_green.xml │ │ ├── ic_radio_frame.xml │ │ ├── ic_radio_selected.xml │ │ ├── ic_refresh.xml │ │ ├── ic_reorder_icon.xml │ │ ├── ic_settings.xml │ │ ├── ic_settings_sound_off.xml │ │ ├── ic_settings_sound_on.xml │ │ ├── ic_sound.xml │ │ ├── ic_sound_off.xml │ │ ├── list_radio_button_selector.xml │ │ ├── shape_stage_empty.xml │ │ └── shape_stage_fill.xml │ │ ├── font │ │ ├── montserrat_extra_bold.ttf │ │ └── roboto_medium.ttf │ │ ├── layout-land │ │ ├── activity_clock_timers.xml │ │ ├── activity_clock_timers_reversed.xml │ │ └── view_clock_menu.xml │ │ ├── layout │ │ ├── activity_app_settings.xml │ │ ├── activity_clock_timers.xml │ │ ├── activity_timer_settings.xml │ │ ├── dialog_fragment_adjust_time.xml │ │ ├── dialog_fragment_edit_stage.xml │ │ ├── dialog_fragment_edit_time_increment.xml │ │ ├── dialog_title_text_view.xml │ │ ├── fragment_settings.xml │ │ ├── fragment_time_control.xml │ │ ├── item_theme.xml │ │ ├── item_view_time.xml │ │ ├── list_stage_item.xml │ │ ├── view_clock_button.xml │ │ ├── view_clock_menu.xml │ │ └── view_styled_button.xml │ │ ├── menu │ │ ├── settings_actions.xml │ │ └── settings_cab_actions.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── raw │ │ ├── chess_clock_pause.mp3 │ │ ├── chess_clock_reset.mp3 │ │ ├── chess_clock_switch1.mp3 │ │ ├── chess_clock_switch2.mp3 │ │ └── chess_clock_time_ended.mp3 │ │ ├── transition │ │ └── fragment_in.xml │ │ ├── values-ar │ │ └── strings.xml │ │ ├── values-fr │ │ └── strings.xml │ │ ├── values-sw360dp │ │ └── dimens.xml │ │ ├── values-sw600dp │ │ └── dimens.xml │ │ ├── values-v19 │ │ └── styles.xml │ │ ├── values-v35 │ │ └── edgetoedge.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── edgetoedge.xml │ │ ├── plurals.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── chess │ └── AutoNameFormatterTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── metadata └── en-US │ ├── changelogs │ ├── 26.txt │ ├── 27.txt │ └── 28.txt │ ├── full_description.txt │ ├── images │ ├── featureGraphic.jpg │ ├── icon.png │ ├── phoneScreenshots │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 5.png │ │ └── 6.png │ ├── sevenInchScreenshots │ │ ├── 1.png │ │ └── 2.png │ └── tenInchScreenshots │ │ ├── 1.png │ │ └── 2.png │ ├── short_description.txt │ └── title.txt └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | .DS_Store 4 | /.idea 5 | *.iml 6 | **/build 7 | 8 | app/certificate 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | ## The basics 4 | * Submit a ticket for your issue, if one does not already exist. 5 | * [Fork](https://help.github.com/articles/fork-a-repo/) the repo. 6 | * Do some work, and file a [pull request](https://help.github.com/articles/about-pull-requests/). 7 | 8 | ## Git guidelines 9 | * Branches should be based on *develop* 10 | * To create a branch: `git checkout develop && git pull && git checkout -b issue-abc-comment` 11 | * Pull requests must be rebased onto the latest from *develop* before they will be merged. 12 | * Make your commits logical units. Commits messages should look like: `git commit -m "Issue abc. Comment goes here"` 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chess.com Chess Clock 2 | 3 | Android version of chess clock mobile app. 4 | 5 | [Get it on Google Play](https://play.google.com/store/apps/details?id=com.chess.clock) 6 | [Get it on F-Droid](https://www.f-droid.org/packages/com.chess.clock/) 7 | 8 | ## Prerequisites 9 | 10 | * Install [Android Studio](http://developer.android.com/sdk/installing/studio.html). 11 | * [Fork](https://help.github.com/articles/fork-a-repo/) the repo 12 | 13 | ## Import the project in Android Studio 14 | 15 | * Open Android Studio. 16 | * Choose Import project. 17 | * Select project folder and press ok. 18 | 19 | ## Run the app via Android Studio 20 | 21 | * Run it using the "play" or "debug" buttons in the UI. 22 | 23 | ## Run via Gradle 24 | 25 | * Start an emulator or connect your device 26 | * Run `./gradlew installDebug` 27 | * You should have a new app called Chess Clock in the app drawer. 28 | * Tap it to open the app 29 | 30 | # Questions? 31 | [File an issue!](https://github.com/ChessCom/android-chessclock/issues) 32 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdk 35 5 | namespace 'com.chess.clock' 6 | 7 | defaultConfig { 8 | minSdkVersion 16 9 | targetSdkVersion 35 10 | versionCode 28 11 | versionName "1.2.2" 12 | } 13 | 14 | buildTypes { 15 | release { 16 | minifyEnabled true 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | debug { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | 25 | buildFeatures { 26 | buildConfig = true 27 | resValues = true 28 | } 29 | 30 | lint { 31 | checkReleaseBuilds false 32 | // Or, if you prefer, you can continue to check for errors in release builds, 33 | // but continue the build even when errors are found: 34 | abortOnError false 35 | } 36 | compileOptions { 37 | sourceCompatibility = '1.8' 38 | targetCompatibility = '1.8' 39 | } 40 | } 41 | 42 | dependencies { 43 | implementation 'androidx.appcompat:appcompat:1.6.1' 44 | implementation 'androidx.recyclerview:recyclerview:1.2.1' 45 | implementation 'com.google.android.material:material:1.11.0' 46 | 47 | testImplementation "junit:junit:4.13.2" 48 | } 49 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | -dontobfuscate 2 | -keepattributes SourceFile,LineNumberTable 3 | 4 | -assumenosideeffects class android.util.Log { 5 | public static *** d(...); 6 | public static *** v(...); 7 | public static *** i(...); 8 | public static *** w(...); 9 | public static *** e(...); 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 28 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/chess/clock/ClockApplication.java: -------------------------------------------------------------------------------- 1 | package com.chess.clock; 2 | 3 | import android.app.Application; 4 | 5 | import com.chess.clock.manager.ChessClockManager; 6 | import com.chess.clock.manager.ChessClockManagerImpl; 7 | 8 | public class ClockApplication extends Application { 9 | private static ChessClockManager clockManager; 10 | 11 | public static ChessClockManager getClockManager() { 12 | if (clockManager == null) { 13 | clockManager = new ChessClockManagerImpl(); 14 | } 15 | return clockManager; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/chess/clock/activities/AppSettingsActivity.java: -------------------------------------------------------------------------------- 1 | package com.chess.clock.activities; 2 | 3 | import android.app.Activity; 4 | import android.app.AlertDialog; 5 | import android.content.res.ColorStateList; 6 | import android.media.MediaPlayer; 7 | import android.os.Bundle; 8 | import android.view.MenuItem; 9 | import android.view.View; 10 | import android.widget.ImageView; 11 | import android.widget.TextView; 12 | 13 | import androidx.appcompat.app.ActionBar; 14 | import androidx.appcompat.widget.SwitchCompat; 15 | import androidx.core.graphics.drawable.DrawableCompat; 16 | import androidx.recyclerview.widget.RecyclerView; 17 | 18 | import com.chess.clock.BuildConfig; 19 | import com.chess.clock.R; 20 | import com.chess.clock.adapters.ThemesAdapter; 21 | import com.chess.clock.entities.AppTheme; 22 | 23 | public class AppSettingsActivity extends BaseActivity { 24 | 25 | private boolean soundsEnabled; 26 | private boolean fullScreenMode; 27 | private ThemesAdapter adapter; 28 | 29 | private ImageView soundImg; 30 | private ImageView fullScreenImg; 31 | 32 | @Override 33 | protected void onCreate(Bundle savedInstanceState) { 34 | super.onCreate(savedInstanceState); 35 | setContentView(R.layout.activity_app_settings); 36 | 37 | soundsEnabled = appData.areSoundsEnabled(); 38 | fullScreenMode = appData.getClockFullScreen(); 39 | 40 | soundImg = findViewById(R.id.soundImg); 41 | fullScreenImg = findViewById(R.id.fullscreenImg); 42 | RecyclerView recycler = findViewById(R.id.themesRecycler); 43 | SwitchCompat fullScreenSwitch = findViewById(R.id.fullscreenSwitch); 44 | SwitchCompat soundsSwitch = findViewById(R.id.soundSwitch); 45 | 46 | ActionBar actionBar = getSupportActionBar(); 47 | if (actionBar != null) { 48 | actionBar.setHomeButtonEnabled(true); 49 | actionBar.setDisplayHomeAsUpEnabled(true); 50 | actionBar.setTitle(R.string.app_settings); 51 | } 52 | 53 | AppTheme selectedTheme = appData.getSelectedTheme(); 54 | adapter = new ThemesAdapter(selectedTheme, theme -> { 55 | ColorStateList tintChecked = theme.switchColorStateList(this); 56 | DrawableCompat.setTintList(fullScreenSwitch.getThumbDrawable(), tintChecked); 57 | DrawableCompat.setTintList(soundsSwitch.getThumbDrawable(), tintChecked); 58 | }); 59 | recycler.setAdapter(adapter); 60 | 61 | fullScreenSwitch.setChecked(fullScreenMode); 62 | soundsSwitch.setChecked(soundsEnabled); 63 | ColorStateList tintChecked = selectedTheme.switchColorStateList(this); 64 | DrawableCompat.setTintList(fullScreenSwitch.getThumbDrawable(), tintChecked); 65 | DrawableCompat.setTintList(soundsSwitch.getThumbDrawable(), tintChecked); 66 | 67 | fullScreenSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { 68 | fullScreenMode = isChecked; 69 | updateUiState(); 70 | }); 71 | MediaPlayer enableSounds = MediaPlayer.create(this, R.raw.chess_clock_pause); 72 | soundsSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { 73 | soundsEnabled = isChecked; 74 | updateUiState(); 75 | if (isChecked) { 76 | enableSounds.start(); 77 | } 78 | }); 79 | 80 | findViewById(R.id.restoreBtn).setOnClickListener(v -> { 81 | TextView titleView = (TextView) View.inflate(this, R.layout.dialog_title_text_view, null); 82 | titleView.setText(R.string.restore_default_controls); 83 | new AlertDialog.Builder(this, R.style.WhiteButtonsDialogTheme) 84 | .setMessage(R.string.restore_default_controls_confirmation_message) 85 | .setCustomTitle(titleView) 86 | .setPositiveButton(R.string.action_confirm, (dialog, id) -> { 87 | BaseActivity activity = AppSettingsActivity.this; 88 | activity.setResult(Activity.RESULT_OK); 89 | activity.finishWithAnimation(); 90 | }) 91 | .setNegativeButton(R.string.action_cancel, (dialog, id) -> { 92 | // dismiss 93 | }) 94 | .create() 95 | .show(); 96 | }); 97 | 98 | updateUiState(); 99 | setVersionInfo(); 100 | } 101 | 102 | private void setVersionInfo() { 103 | TextView versionTv = findViewById(R.id.versionTv); 104 | String version = getString(R.string.version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE); 105 | versionTv.setText(version); 106 | } 107 | 108 | private void updateUiState() { 109 | soundImg.setImageResource(soundsEnabled ? R.drawable.ic_settings_sound_on : R.drawable.ic_settings_sound_off); 110 | fullScreenImg.setImageResource(fullScreenMode ? R.drawable.ic_fullscreen : R.drawable.ic_fullscreen_exit); 111 | } 112 | 113 | @Override 114 | protected void onPause() { 115 | super.onPause(); 116 | appData.saveAppSetup( 117 | adapter.selectedTheme, 118 | fullScreenMode, 119 | soundsEnabled 120 | ); 121 | } 122 | 123 | @Override 124 | public boolean onOptionsItemSelected(MenuItem item) { 125 | if (item.getItemId() == android.R.id.home) { 126 | finishWithAnimation(); 127 | return true; 128 | } 129 | return super.onOptionsItemSelected(item); 130 | } 131 | 132 | @Override 133 | public void onBackPressed() { 134 | finishWithAnimation(); 135 | } 136 | 137 | } -------------------------------------------------------------------------------- /app/src/main/java/com/chess/clock/activities/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.chess.clock.activities; 2 | 3 | import android.os.Bundle; 4 | import android.view.WindowManager; 5 | 6 | import androidx.annotation.Nullable; 7 | import androidx.appcompat.app.AppCompatActivity; 8 | 9 | import com.chess.clock.ClockApplication; 10 | import com.chess.clock.R; 11 | import com.chess.clock.entities.AppTheme; 12 | import com.chess.clock.manager.ChessClockManager; 13 | import com.chess.clock.statics.AppData; 14 | 15 | public class BaseActivity extends AppCompatActivity { 16 | /** 17 | * Shared preferences wrapper 18 | */ 19 | protected AppData appData; 20 | 21 | public AppTheme selectedTheme; 22 | 23 | @Override 24 | protected void onCreate(@Nullable Bundle savedInstanceState) { 25 | getTheme().applyStyle(R.style.OptOutEdgeToEdgeEnforcement, false); 26 | super.onCreate(savedInstanceState); 27 | appData = new AppData(getApplicationContext()); 28 | selectedTheme = appData.getSelectedTheme(); 29 | } 30 | 31 | @Override 32 | protected void onResume() { 33 | super.onResume(); 34 | this.selectedTheme = appData.getSelectedTheme(); 35 | } 36 | 37 | public ChessClockManager getClockManager() { 38 | return ClockApplication.getClockManager(); 39 | } 40 | 41 | public void hideStatusBar() { 42 | WindowManager.LayoutParams attrs = getWindow().getAttributes(); 43 | attrs.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN; 44 | getWindow().setAttributes(attrs); 45 | } 46 | 47 | public void showStatusBar() { 48 | WindowManager.LayoutParams attrs = getWindow().getAttributes(); 49 | attrs.flags &= ~WindowManager.LayoutParams.FLAG_FULLSCREEN; 50 | getWindow().setAttributes(attrs); 51 | } 52 | 53 | public void finishWithAnimation() { 54 | finish(); 55 | overridePendingTransition(R.anim.left_to_right_in, R.anim.left_to_right_full); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/com/chess/clock/activities/ClockSoundManager.java: -------------------------------------------------------------------------------- 1 | package com.chess.clock.activities; 2 | 3 | import android.content.Context; 4 | import android.media.AudioManager; 5 | import android.media.SoundPool; 6 | 7 | import com.chess.clock.R; 8 | 9 | import java.util.HashSet; 10 | 11 | enum ClockSound { 12 | PLAYER_ONE_MOVE, PLAYER_TWO_MOVE, GAME_FINISHED, RESET_CLOCK, MENU_ACTION 13 | } 14 | 15 | interface ClockSoundManager { 16 | void init(Context context); 17 | 18 | void releaseSounds(); 19 | 20 | void setSoundsEnabled(boolean enabled); 21 | 22 | boolean areSoundsEnabled(); 23 | 24 | void playSound(ClockSound sound, AudioManager manager); 25 | 26 | void toggleSound(); 27 | } 28 | 29 | class ClockSoundManagerImpl implements ClockSoundManager { 30 | private static final int NO_LOOP = 0; 31 | private static final int NO_SOUND_ID = 0; 32 | private static final float PLAYBACK_RATE = 1f; 33 | private static final int SOUND_PRIORITY = 1; 34 | 35 | public boolean soundsEnabled = true; 36 | 37 | private SoundPool soundPool; 38 | private final HashSet preparedSoundsIds = new HashSet(); 39 | 40 | private int menuActionId = NO_SOUND_ID; 41 | private int playerOneMoveId = NO_SOUND_ID; 42 | private int playerTwoMoveId = NO_SOUND_ID; 43 | private int gameFinishedId = NO_SOUND_ID; 44 | private int clockResetId = NO_SOUND_ID; 45 | 46 | @Override 47 | public void init(Context context) { 48 | soundPool = new SoundPool(5, AudioManager.STREAM_MUSIC, 0); 49 | soundPool.setOnLoadCompleteListener((soundPool, sampleId, status) -> preparedSoundsIds.add(sampleId)); 50 | menuActionId = soundPool.load(context, R.raw.chess_clock_pause, 1); 51 | playerOneMoveId = soundPool.load(context, R.raw.chess_clock_switch1, 1); 52 | playerTwoMoveId = soundPool.load(context, R.raw.chess_clock_switch2, 1); 53 | gameFinishedId = soundPool.load(context, R.raw.chess_clock_time_ended, 1); 54 | clockResetId = soundPool.load(context, R.raw.chess_clock_reset, 1); 55 | } 56 | 57 | @Override 58 | public void releaseSounds() { 59 | soundPool.release(); 60 | } 61 | 62 | @Override 63 | public void setSoundsEnabled(boolean soundsEnabled) { 64 | this.soundsEnabled = soundsEnabled; 65 | } 66 | 67 | @Override 68 | public boolean areSoundsEnabled() { 69 | return soundsEnabled; 70 | } 71 | 72 | @Override 73 | public void playSound(ClockSound sound, AudioManager manager) { 74 | if (!soundsEnabled) return; 75 | int soundId = clockSoundId(sound); 76 | 77 | if (soundId == NO_SOUND_ID) return; // sound failed to load 78 | if (!preparedSoundsIds.contains(soundId)) return; // sound not prepared 79 | 80 | float actualVolume = (float) manager.getStreamVolume(AudioManager.STREAM_MUSIC); 81 | float maxVolume = (float) manager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); 82 | float soundVolume = actualVolume / maxVolume; 83 | 84 | soundPool.play(soundId, soundVolume, soundVolume, SOUND_PRIORITY, NO_LOOP, PLAYBACK_RATE); 85 | } 86 | 87 | @Override 88 | public void toggleSound() { 89 | soundsEnabled = !soundsEnabled; 90 | } 91 | 92 | private Integer clockSoundId(ClockSound sound) { 93 | int soundId = NO_SOUND_ID; 94 | switch (sound) { 95 | case PLAYER_ONE_MOVE: 96 | soundId = playerOneMoveId; 97 | break; 98 | case PLAYER_TWO_MOVE: 99 | soundId = playerTwoMoveId; 100 | break; 101 | case GAME_FINISHED: 102 | soundId = gameFinishedId; 103 | break; 104 | case RESET_CLOCK: 105 | soundId = clockResetId; 106 | break; 107 | case MENU_ACTION: 108 | soundId = menuActionId; 109 | break; 110 | } 111 | return soundId; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /app/src/main/java/com/chess/clock/adapters/ThemesAdapter.java: -------------------------------------------------------------------------------- 1 | package com.chess.clock.adapters; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.ImageView; 8 | 9 | import androidx.annotation.NonNull; 10 | import androidx.cardview.widget.CardView; 11 | import androidx.core.content.ContextCompat; 12 | import androidx.recyclerview.widget.RecyclerView; 13 | 14 | import com.chess.clock.R; 15 | import com.chess.clock.entities.AppTheme; 16 | import com.chess.clock.views.ViewUtils; 17 | 18 | public class ThemesAdapter extends RecyclerView.Adapter { 19 | public AppTheme selectedTheme; 20 | private final ThemeSelectListener listener; 21 | 22 | public ThemesAdapter(AppTheme initialTheme, ThemeSelectListener listener) { 23 | setHasStableIds(true); 24 | this.selectedTheme = initialTheme; 25 | this.listener = listener; 26 | } 27 | 28 | @NonNull 29 | @Override 30 | public ThemeViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 31 | View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_theme, parent, false); 32 | return new ThemeViewHolder(view); 33 | } 34 | 35 | @SuppressLint("NotifyDataSetChanged") 36 | @Override 37 | public void onBindViewHolder(@NonNull ThemeViewHolder holder, int position) { 38 | AppTheme theme = AppTheme.fromInt(position); 39 | holder.bind(theme, theme == selectedTheme); 40 | holder.themeCard.setOnClickListener(v -> { 41 | selectedTheme = theme; 42 | listener.onSelectTheme(selectedTheme); 43 | notifyDataSetChanged(); 44 | }); 45 | } 46 | 47 | @Override 48 | public int getItemCount() { 49 | return AppTheme.values().length; 50 | } 51 | 52 | public static class ThemeViewHolder extends RecyclerView.ViewHolder { 53 | private final ImageView checkmarkImg; 54 | private final CardView themeCard; 55 | 56 | public ThemeViewHolder(@NonNull View itemView) { 57 | super(itemView); 58 | checkmarkImg = itemView.findViewById(R.id.checkmarkImg); 59 | themeCard = itemView.findViewById(R.id.themeCard); 60 | } 61 | 62 | public void bind(AppTheme appTheme, boolean selected) { 63 | int color = ContextCompat.getColor(itemView.getContext(), appTheme.primaryColorRes); 64 | themeCard.setCardBackgroundColor(color); 65 | ViewUtils.showView(checkmarkImg, selected); 66 | } 67 | } 68 | 69 | public interface ThemeSelectListener { 70 | void onSelectTheme(AppTheme theme); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/src/main/java/com/chess/clock/adapters/TimeRowMoveCallback.java: -------------------------------------------------------------------------------- 1 | package com.chess.clock.adapters; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.recyclerview.widget.ItemTouchHelper; 5 | import androidx.recyclerview.widget.RecyclerView; 6 | 7 | public class TimeRowMoveCallback extends ItemTouchHelper.Callback { 8 | 9 | TimeItemTouchCallback touchCallback; 10 | 11 | public TimeRowMoveCallback(TimeItemTouchCallback callback) { 12 | this.touchCallback = callback; 13 | } 14 | 15 | @Override 16 | public boolean isLongPressDragEnabled() { 17 | return true; 18 | } 19 | 20 | @Override 21 | public boolean isItemViewSwipeEnabled() { 22 | return false; 23 | } 24 | 25 | @Override 26 | public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { 27 | int drag = ItemTouchHelper.UP | ItemTouchHelper.DOWN; 28 | return makeMovementFlags(drag, 0); 29 | } 30 | 31 | @Override 32 | public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) { 33 | this.touchCallback.onTimeItemMoved(viewHolder.getAbsoluteAdapterPosition(), target.getAbsoluteAdapterPosition()); 34 | return true; 35 | } 36 | 37 | @Override 38 | public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) { 39 | 40 | } 41 | 42 | public interface TimeItemTouchCallback { 43 | void onTimeItemMoved(int from, int to); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/com/chess/clock/dialog/AdjustTimeDialogFragment.java: -------------------------------------------------------------------------------- 1 | package com.chess.clock.dialog; 2 | 3 | import android.content.res.ColorStateList; 4 | import android.content.res.Configuration; 5 | import android.os.Build; 6 | import android.os.Bundle; 7 | import android.view.View; 8 | import android.widget.EditText; 9 | 10 | import androidx.annotation.NonNull; 11 | import androidx.annotation.Nullable; 12 | 13 | import com.chess.clock.R; 14 | import com.chess.clock.activities.BaseActivity; 15 | import com.chess.clock.entities.AppTheme; 16 | import com.chess.clock.entities.ClockTime; 17 | import com.chess.clock.util.ClockUtils; 18 | 19 | public class AdjustTimeDialogFragment extends FullScreenDialogFragment { 20 | 21 | public static final String TAG = "AdjustTimeDialogFragment"; 22 | private static final String ARG_TIME_KEY = "arg_time_key"; 23 | private static final String ARG_FIRST_PLAYER_KEY = "arg_first_player_key"; 24 | 25 | @Override 26 | int layoutRes() { 27 | return R.layout.dialog_fragment_adjust_time; 28 | } 29 | 30 | @Override 31 | public int bgColorRes() { 32 | return R.color.black_40; 33 | } 34 | 35 | public static AdjustTimeDialogFragment newInstance(Long time, boolean firstPlayer) { 36 | 37 | Bundle args = new Bundle(); 38 | args.putBoolean(ARG_FIRST_PLAYER_KEY, firstPlayer); 39 | args.putLong(ARG_TIME_KEY, time); 40 | 41 | AdjustTimeDialogFragment fragment = new AdjustTimeDialogFragment(); 42 | fragment.setArguments(args); 43 | return fragment; 44 | } 45 | 46 | @Override 47 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 48 | super.onViewCreated(view, savedInstanceState); 49 | boolean firstPlayer = requireArguments().getBoolean(ARG_FIRST_PLAYER_KEY); 50 | if (!firstPlayer && getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { 51 | view.setRotation(180); 52 | } 53 | EditText hoursEt = view.findViewById(R.id.hoursEt); 54 | EditText minutesEt = view.findViewById(R.id.minutesEt); 55 | EditText secondsEt = view.findViewById(R.id.secondsEt); 56 | 57 | view.findViewById(R.id.saveBtn).setOnClickListener(v -> { 58 | int hours = ClockUtils.getIntOrZero(hoursEt); 59 | int minutes = ClockUtils.getIntOrZero(minutesEt); 60 | int seconds = ClockUtils.getIntOrZero(secondsEt); 61 | ((TimeAdjustmentsListener) requireActivity()).onTimeAdjustmentsConfirmed( 62 | ClockUtils.durationMillis(hours, minutes, seconds), 63 | firstPlayer 64 | ); 65 | dismissAllowingStateLoss(); 66 | }); 67 | view.findViewById(R.id.cancelBtn).setOnClickListener(v -> dismissAllowingStateLoss()); 68 | 69 | if (savedInstanceState == null) { 70 | ClockTime clockTime = ClockTime.calibrated(requireArguments().getLong(ARG_TIME_KEY)); 71 | hoursEt.setText(ClockUtils.twoDecimalPlacesFormat(Math.min(clockTime.hours, 99))); 72 | minutesEt.setText(ClockUtils.twoDecimalPlacesFormat(clockTime.minutes)); 73 | secondsEt.setText(ClockUtils.twoDecimalPlacesFormat(clockTime.seconds)); 74 | } 75 | 76 | ClockUtils.setClockTextWatcher(minutesEt); 77 | ClockUtils.setClockTextWatcher(secondsEt); 78 | 79 | AppTheme theme = ((BaseActivity) requireActivity()).selectedTheme; 80 | if (theme != null) { 81 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 82 | ColorStateList tintList = theme.colorStateListFocused(requireContext()); 83 | hoursEt.setBackgroundTintList(tintList); 84 | minutesEt.setBackgroundTintList(tintList); 85 | secondsEt.setBackgroundTintList(tintList); 86 | } 87 | } 88 | } 89 | 90 | public interface TimeAdjustmentsListener { 91 | void onTimeAdjustmentsConfirmed(long timeMs, boolean firstPlayer); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /app/src/main/java/com/chess/clock/dialog/FullScreenDialogFragment.java: -------------------------------------------------------------------------------- 1 | package com.chess.clock.dialog; 2 | 3 | import android.app.Dialog; 4 | import android.graphics.drawable.ColorDrawable; 5 | import android.os.Bundle; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.FrameLayout; 10 | 11 | import androidx.annotation.NonNull; 12 | import androidx.annotation.Nullable; 13 | import androidx.core.content.ContextCompat; 14 | import androidx.fragment.app.DialogFragment; 15 | 16 | import com.chess.clock.R; 17 | 18 | abstract class FullScreenDialogFragment extends DialogFragment { 19 | abstract int layoutRes(); 20 | 21 | public int bgColorRes() { 22 | return R.color.gray_background; 23 | } 24 | 25 | public int theme() { 26 | return R.style.AppTheme_DialogFullScreen; 27 | } 28 | 29 | @Nullable 30 | @Override 31 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 32 | return inflater.inflate(layoutRes(), container, false); 33 | } 34 | 35 | @NonNull 36 | @Override 37 | public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { 38 | Dialog dialog = new Dialog(requireActivity(), theme()); 39 | FrameLayout content = new FrameLayout(requireActivity()); 40 | content.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); 41 | dialog.setContentView(content); 42 | dialog.getWindow().setBackgroundDrawable( 43 | new ColorDrawable(ContextCompat.getColor(requireContext(), bgColorRes())) 44 | ); 45 | dialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); 46 | return dialog; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/chess/clock/engine/ClockPlayer.java: -------------------------------------------------------------------------------- 1 | package com.chess.clock.engine; 2 | 3 | public enum ClockPlayer { 4 | ONE, TWO; 5 | 6 | public static ClockPlayer ofBoolean(boolean playerOne) { 7 | return playerOne ? ONE : TWO; 8 | } 9 | 10 | public boolean isFirstPlayer() { 11 | return this == ONE; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/chess/clock/engine/TimeControl.java: -------------------------------------------------------------------------------- 1 | package com.chess.clock.engine; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | import androidx.annotation.NonNull; 7 | 8 | import com.chess.clock.util.Args; 9 | 10 | /** 11 | *

12 | * This class represents the time control of a single player in a chess game. At the most basic 13 | * level, time controls can simply be expressed as the time each player has in order to make all of 14 | * his moves during a game. Commonly, these time controls are called “Game in X”. For instance, you 15 | * might have a time control named G/60 – shorthand for game in 60 minutes. 16 | *

17 | *

18 | *

Multiple Time Control Stages

19 | *

20 | *

21 | * Time controls may be broken up into multiple stages. This ensures that while a game might 22 | * be long, players are forced to reach a certain point of the game earlier. One multiple time 23 | * control format seen frequently in major tournaments is 40/120, G/60. This time control requires 24 | * players to make at least 40 moves in their first two hours of playing time, then gives each 25 | * player another hour with which to finish the remainder of the game. 26 | *

27 | *

28 | *

Increment and Time Delay

29 | *

30 | *

31 | * With an increment, players have time added to their clock after every move that’s completed, 32 | * thus ensuring that they’ll always have at least the increment time to make a move. Time delay 33 | * works slightly differently. Instead of adding time to the clock, a time delay creates a period 34 | * after your opponent moves during which your clock will not run. 35 | *

36 | */ 37 | public class TimeControl implements Parcelable, Cloneable, StageManager.StageManagerListener { 38 | 39 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 40 | public TimeControl createFromParcel(Parcel source) { 41 | return new TimeControl(source); 42 | } 43 | 44 | public TimeControl[] newArray(int size) { 45 | return new TimeControl[size]; 46 | } 47 | }; 48 | 49 | /** 50 | * TimeControl identifier. 51 | */ 52 | private String mName; 53 | 54 | /** 55 | * Stage Manager associated with Time Control. 56 | */ 57 | private StageManager mStageManager; 58 | 59 | /** 60 | * Listener used to dispatch time control update events. 61 | * 62 | * @see TimeControlListener 63 | */ 64 | private TimeControlListener mTimeControlListener; 65 | 66 | /** 67 | * Simple constructor to use when creating a TimeControl. 68 | * 69 | * @param name Name identifier. 70 | * @param stages stages of the TimeControl. 71 | * @throws java.lang.NullPointerException if StageManager or TimeIncrement are not provided. 72 | */ 73 | public TimeControl(String name, Stage[] stages) { 74 | Args.checkForNull(stages); 75 | 76 | mName = name; 77 | mStageManager = new StageManager(stages); 78 | 79 | // Set up listener for Stage Manager. 80 | mStageManager.setStageManagerListener(this); 81 | } 82 | 83 | private TimeControl(Parcel parcel) { 84 | this.readFromParcel(parcel); 85 | } 86 | 87 | /** 88 | * Check if TimeControl object is equal to this one. 89 | * 90 | * @param tc TimeControl Object. 91 | * @return True if relevant contents are equal. 92 | */ 93 | public boolean isEqual(TimeControl tc) { 94 | 95 | boolean equalNames = (mName == null && tc.getName() == null) || 96 | mName.equals(tc.getName()); 97 | 98 | return (equalNames && mStageManager.isEqual(tc.getStageManager())); 99 | } 100 | 101 | /** 102 | * Register a callback to be invoked when time control updates. 103 | * 104 | * @param listener The callback that will run 105 | */ 106 | public void setTimeControlListener(TimeControlListener listener) { 107 | Args.checkForNull(listener); 108 | mTimeControlListener = listener; 109 | } 110 | 111 | /** 112 | * The name identifier of the time control. 113 | * 114 | * @return Time control name. 115 | */ 116 | public String getName() { 117 | return mName; 118 | } 119 | 120 | /** 121 | * Set the name identifier of the time control. 122 | * 123 | * @param name Time control name. 124 | */ 125 | public void setName(String name) { 126 | mName = name; 127 | } 128 | 129 | /** 130 | * Gets the Stage manager associated with this time control. 131 | * 132 | * @return Stage manager associated with this time control. 133 | */ 134 | public StageManager getStageManager() { 135 | return mStageManager; 136 | } 137 | 138 | private void readFromParcel(Parcel parcel) { 139 | mName = parcel.readString(); 140 | mStageManager = parcel.readParcelable(StageManager.class.getClassLoader()); 141 | mStageManager.setStageManagerListener(this); 142 | } 143 | 144 | @Override 145 | public void writeToParcel(Parcel parcel, int flags) { 146 | parcel.writeString(mName); 147 | parcel.writeParcelable(mStageManager, flags); 148 | } 149 | 150 | @Override 151 | public int describeContents() { 152 | return 0; 153 | } 154 | 155 | /** 156 | * /** 157 | * {@inheritDoc} 158 | */ 159 | @Override 160 | public void onNewStageUpdate(Stage stage) { 161 | Args.checkForNull(stage); 162 | 163 | if (mTimeControlListener != null) { 164 | mTimeControlListener.onStageUpdate(stage); 165 | } 166 | } 167 | 168 | /** 169 | * /** 170 | * {@inheritDoc} 171 | */ 172 | @Override 173 | public void onMoveCountUpdate(int moveCount) { 174 | if (mTimeControlListener != null) { 175 | mTimeControlListener.onMoveCountUpdate(moveCount); 176 | } 177 | } 178 | 179 | @NonNull 180 | @Override 181 | public Object clone() throws CloneNotSupportedException { 182 | TimeControl clone = (TimeControl) super.clone(); 183 | 184 | // Clone StageManager object and set this clone as his listener. 185 | clone.mStageManager = (StageManager) mStageManager.clone(); 186 | clone.mStageManager.setStageManagerListener(clone); 187 | 188 | clone.mTimeControlListener = null; 189 | return clone; 190 | } 191 | 192 | /** 193 | * Interface definition for a callback to be invoked when the player clock gets updated. 194 | */ 195 | public interface TimeControlListener { 196 | 197 | /** 198 | * Called when new game stage begins. 199 | * 200 | * @param stage The current game stage. 201 | */ 202 | void onStageUpdate(Stage stage); 203 | 204 | /** 205 | * Called when the move count is updated. 206 | */ 207 | void onMoveCountUpdate(int moves); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /app/src/main/java/com/chess/clock/engine/TimeControlDefaults.java: -------------------------------------------------------------------------------- 1 | package com.chess.clock.engine; 2 | 3 | import android.content.Context; 4 | 5 | import com.chess.clock.R; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Arrays; 9 | import java.util.List; 10 | 11 | public class TimeControlDefaults { 12 | 13 | private static final List PRESETS = Arrays.asList( 14 | new TimePreset(1), 15 | new TimePreset(1, 1), 16 | new TimePreset(2, 1), 17 | new TimePreset(3), 18 | new TimePreset(3, 2), 19 | new TimePreset(5), 20 | new TimePreset(5, 5), 21 | new TimePreset(10), 22 | new TimePreset(15, 10), 23 | new TimePreset(20), 24 | new TimePreset(30) 25 | ); 26 | 27 | /** 28 | * index of `10 min` preset 29 | */ 30 | public static long DEFAULT_TIME_ID = 7L; 31 | 32 | /** 33 | * Creates default TimeControl list, saves it on shared preferences. 34 | * 35 | * @return Default TimeControl list. 36 | */ 37 | public static ArrayList buildDefaultTimeControlsList(Context context) { 38 | 39 | ArrayList timeControls = new ArrayList<>(); 40 | for (int i = 0, presetsSize = PRESETS.size(); i < presetsSize; i++) { 41 | TimePreset timePreset = PRESETS.get(i); 42 | timeControls.add(timePreset.toTimeControl(context, i)); 43 | } 44 | 45 | // Saving default time controls 46 | TimeControlParser.saveTimeControls(context, timeControls); 47 | TimeControlParser.saveSelectedTimeControlId(context, TimeControlDefaults.DEFAULT_TIME_ID); 48 | 49 | return timeControls; 50 | } 51 | 52 | private static class TimePreset { 53 | int minutes; 54 | int incrementSeconds; 55 | 56 | TimePreset(int minutes) { 57 | this.minutes = minutes; 58 | incrementSeconds = 0; 59 | } 60 | 61 | TimePreset(int minutes, int incrementSeconds) { 62 | this.minutes = minutes; 63 | this.incrementSeconds = incrementSeconds; 64 | } 65 | 66 | String getName(Context context) { 67 | if (incrementSeconds == 0) { 68 | return context.getString(R.string.x_min, minutes); 69 | } else { 70 | return context.getString(R.string.x_min_y_sec, minutes, incrementSeconds); 71 | } 72 | } 73 | 74 | TimeControlWrapper toTimeControl(Context context, int defaultIndex) { 75 | TimeIncrement timeIncrement = new TimeIncrement(TimeIncrement.Type.FISCHER, incrementSeconds * 1000L); 76 | Stage stage = new Stage(0, minutes * 60 * 1000L, timeIncrement); 77 | TimeControl timeControlOne = new TimeControl(getName(context), new Stage[]{stage}); 78 | TimeControl timeControlTwo = new TimeControl(getName(context), new Stage[]{stage}); 79 | return new TimeControlWrapper(defaultIndex, defaultIndex, timeControlOne, timeControlTwo); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /app/src/main/java/com/chess/clock/engine/TimeControlWrapper.java: -------------------------------------------------------------------------------- 1 | package com.chess.clock.engine; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | import androidx.annotation.NonNull; 7 | 8 | public class TimeControlWrapper implements Parcelable, Cloneable { 9 | private TimeControl mTimeControlPlayerOne; 10 | private TimeControl mTimeControlPlayerTwo; 11 | 12 | private boolean mSameAsPlayerOne; 13 | private long id; 14 | private int order; 15 | 16 | public TimeControlWrapper( 17 | long id, 18 | int order, 19 | TimeControl playerOne, 20 | TimeControl playerTwo 21 | ) { 22 | this.id = id; 23 | this.order = order; 24 | mTimeControlPlayerOne = playerOne; 25 | mTimeControlPlayerTwo = playerTwo; 26 | mSameAsPlayerOne = true; 27 | } 28 | 29 | private TimeControlWrapper(Parcel in) { 30 | id = in.readLong(); 31 | order = in.readInt(); 32 | mTimeControlPlayerOne = in.readParcelable(TimeControl.class.getClassLoader()); 33 | mTimeControlPlayerTwo = in.readParcelable(TimeControl.class.getClassLoader()); 34 | mSameAsPlayerOne = in.readByte() != 0; 35 | } 36 | 37 | @Override 38 | public void writeToParcel(Parcel dest, int flags) { 39 | dest.writeLong(id); 40 | dest.writeInt(order); 41 | dest.writeParcelable(mTimeControlPlayerOne, flags); 42 | dest.writeParcelable(mTimeControlPlayerTwo, flags); 43 | dest.writeByte((byte) (mSameAsPlayerOne ? 1 : 0)); 44 | } 45 | 46 | 47 | public static final Creator CREATOR = new Creator() { 48 | @Override 49 | public TimeControlWrapper createFromParcel(Parcel in) { 50 | return new TimeControlWrapper(in); 51 | } 52 | 53 | @Override 54 | public TimeControlWrapper[] newArray(int size) { 55 | return new TimeControlWrapper[size]; 56 | } 57 | }; 58 | 59 | public TimeControl getTimeControlPlayerOne() { 60 | return mTimeControlPlayerOne; 61 | } 62 | 63 | public void setTimeControlPlayerOne(TimeControl timeControl) { 64 | mTimeControlPlayerOne = timeControl; 65 | } 66 | 67 | public TimeControl getTimeControlPlayerTwo() { 68 | return mTimeControlPlayerTwo; 69 | } 70 | 71 | public void setTimeControlPlayerTwo(TimeControl timeControl) { 72 | mTimeControlPlayerTwo = timeControl; 73 | } 74 | 75 | public boolean isSameAsPlayerOne() { 76 | return mSameAsPlayerOne; 77 | } 78 | 79 | public void setSameAsPlayerOne(boolean sameAsPlayerOne) { 80 | mSameAsPlayerOne = sameAsPlayerOne; 81 | } 82 | 83 | @NonNull 84 | @Override 85 | public Object clone() throws CloneNotSupportedException { 86 | TimeControlWrapper clone = (TimeControlWrapper) super.clone(); 87 | 88 | // Clone StageManager object and set this clone as his listener. 89 | clone.mTimeControlPlayerOne = (TimeControl) mTimeControlPlayerOne.clone(); 90 | clone.mTimeControlPlayerTwo = (TimeControl) mTimeControlPlayerTwo.clone(); 91 | clone.mSameAsPlayerOne = mSameAsPlayerOne; 92 | clone.order = order; 93 | clone.id = id; 94 | return clone; 95 | } 96 | 97 | @Override 98 | public int describeContents() { 99 | return 0; 100 | } 101 | 102 | public boolean isEqual(TimeControlWrapper wrapper) { 103 | return mTimeControlPlayerOne.isEqual(wrapper.getTimeControlPlayerOne()) && 104 | mTimeControlPlayerTwo.isEqual(wrapper.getTimeControlPlayerTwo()) && 105 | mSameAsPlayerOne == wrapper.isSameAsPlayerOne() && 106 | id == wrapper.id && 107 | order == wrapper.order; 108 | } 109 | 110 | public boolean bothUsersHaveAtLeastOneStage() { 111 | if (mSameAsPlayerOne) { 112 | return mTimeControlPlayerOne.getStageManager().getTotalStages() > 0; 113 | } else { 114 | return mTimeControlPlayerOne.getStageManager().getTotalStages() > 0 && mTimeControlPlayerTwo.getStageManager().getTotalStages() > 0; 115 | } 116 | } 117 | 118 | public long getId() { 119 | return id; 120 | } 121 | 122 | public int getOrder() { 123 | return order; 124 | } 125 | 126 | public void setOrder(int newOrder) { 127 | this.order = newOrder; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /app/src/main/java/com/chess/clock/engine/TimeIncrement.java: -------------------------------------------------------------------------------- 1 | package com.chess.clock.engine; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | import android.util.Log; 6 | 7 | import androidx.annotation.NonNull; 8 | 9 | import com.chess.clock.entities.ClockTime; 10 | 11 | /** 12 | * A small amount of time that is added for each game addMove. 13 | */ 14 | public class TimeIncrement implements Parcelable, Cloneable { 15 | 16 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 17 | public TimeIncrement createFromParcel(Parcel source) { 18 | return new TimeIncrement(source); 19 | } 20 | 21 | public TimeIncrement[] newArray(int size) { 22 | return new TimeIncrement[size]; 23 | } 24 | }; 25 | private static final String TAG = TimeIncrement.class.getName(); 26 | /** 27 | * TimeIncrement type used 28 | */ 29 | private Type mType; 30 | 31 | /** 32 | * Time Increment amount in milliseconds 33 | */ 34 | private long mValue; 35 | 36 | /** 37 | * @param mType Time control TimeIncrement StageType. 38 | * @param value TimeIncrement value in milliseconds 39 | * @see TimeIncrement.Type 40 | */ 41 | public TimeIncrement(Type mType, long value) { 42 | this.mType = mType; 43 | this.mValue = validateIncrementValue(value); 44 | } 45 | 46 | private TimeIncrement(Parcel parcel) { 47 | this.readFromParcel(parcel); 48 | } 49 | 50 | public static TimeIncrement defaultIncrement() { 51 | return new TimeIncrement(TimeIncrement.Type.FISCHER, 0); 52 | } 53 | 54 | /** 55 | * @return The increment type. 56 | */ 57 | public Type getType() { 58 | return mType; 59 | } 60 | 61 | /** 62 | * Set the type of the time increment. 63 | * 64 | * @param type Type 65 | */ 66 | public void setType(Type type) { 67 | mType = type; 68 | } 69 | 70 | /** 71 | * @return Increment time value in milliseconds. 72 | */ 73 | public long getValue() { 74 | return mValue; 75 | } 76 | 77 | /** 78 | * @return Int array with {hour,minute,second} 79 | */ 80 | public ClockTime getDuration() { 81 | return ClockTime.raw(mValue); 82 | } 83 | 84 | /** 85 | * Check if TimeIncrement object is equal to this one. 86 | * 87 | * @param ti TimeIncrement Object. 88 | * @return True if relevant contents are equal. 89 | */ 90 | public boolean isEqual(TimeIncrement ti) { 91 | 92 | if (mValue != ti.getValue()) { 93 | Log.i(TAG, "Value not equal."); 94 | return false; 95 | } else if (mType.getValue() != ti.getType().getValue()) { 96 | Log.i(TAG, "Type not equal."); 97 | return false; 98 | } else { 99 | return true; 100 | } 101 | } 102 | 103 | /** 104 | * Get formatted string ready to UI info display. 105 | * 106 | * @return String representing info content of TimeIncrement. 107 | */ 108 | @NonNull 109 | public String toString() { 110 | if (mType == Type.NONE) { 111 | return mType.typeName; 112 | } else { 113 | return mType.typeName + ", " + ClockTime.raw(mValue).toMinutesFormat(); 114 | } 115 | } 116 | 117 | /** 118 | * Avoid non valid increment values. 119 | * 120 | * @param value TimeIncrement value in milliseconds 121 | * @return TimeIncrement value or zero if negative. 122 | */ 123 | private long validateIncrementValue(long value) { 124 | return Math.max(0, value); 125 | } 126 | 127 | private void readFromParcel(Parcel parcel) { 128 | mType = Type.fromInteger(parcel.readInt()); 129 | mValue = parcel.readLong(); 130 | } 131 | 132 | @Override 133 | public void writeToParcel(Parcel parcel, int flags) { 134 | parcel.writeInt(mType.getValue()); 135 | parcel.writeLong(mValue); 136 | } 137 | 138 | @Override 139 | public int describeContents() { 140 | return 0; 141 | } 142 | 143 | @NonNull 144 | @Override 145 | public Object clone() throws CloneNotSupportedException { 146 | TimeIncrement cloned = (TimeIncrement) super.clone(); 147 | cloned.mType = Type.fromInteger(mType.getValue()); 148 | return cloned; 149 | } 150 | 151 | /** 152 | * Time Control TimeIncrement StageType. 153 | */ 154 | public enum Type { 155 | 156 | /** 157 | * The player's clock starts after the delay period. 158 | */ 159 | DELAY(0, "Delay"), 160 | 161 | /** 162 | * Players receive the used portion of the increment at the end of each turn. 163 | */ 164 | BRONSTEIN(1, "Bronstein"), 165 | 166 | /** 167 | * Players receive a full increment at the end of each turn. 168 | */ 169 | FISCHER(2, "Fischer"), 170 | 171 | /** 172 | * No increment. 173 | */ 174 | NONE(3, "None"); 175 | 176 | private final int value; 177 | private final String typeName; 178 | 179 | Type(int value, String typeName) { 180 | this.value = value; 181 | this.typeName = typeName; 182 | } 183 | 184 | public static Type fromInteger(int type) { 185 | switch (type) { 186 | case 0: 187 | return DELAY; 188 | case 1: 189 | return BRONSTEIN; 190 | case 2: 191 | return FISCHER; 192 | case 3: 193 | return NONE; 194 | } 195 | return null; 196 | } 197 | 198 | public int getValue() { 199 | return value; 200 | } 201 | } 202 | } -------------------------------------------------------------------------------- /app/src/main/java/com/chess/clock/entities/AppTheme.java: -------------------------------------------------------------------------------- 1 | package com.chess.clock.entities; 2 | 3 | import android.content.Context; 4 | import android.content.res.ColorStateList; 5 | 6 | import androidx.annotation.ColorRes; 7 | import androidx.core.content.ContextCompat; 8 | 9 | import com.chess.clock.R; 10 | 11 | public enum AppTheme { 12 | GREEN(R.color.green), 13 | BLUE(R.color.blue), 14 | ORANGE(R.color.orange), 15 | TURQUOISE(R.color.turquoise), 16 | GOLD(R.color.gold), 17 | PINK(R.color.pink); 18 | 19 | public final int primaryColorRes; 20 | 21 | AppTheme(@ColorRes int colorRes) { 22 | this.primaryColorRes = colorRes; 23 | } 24 | 25 | public static AppTheme fromInt(int position) { 26 | for (AppTheme theme : AppTheme.values()) { 27 | if (theme.ordinal() == position) { 28 | return theme; 29 | } 30 | } 31 | throw new AssertionError("no app theme for position: " + position); 32 | } 33 | 34 | public ColorStateList switchColorStateList(Context context) { 35 | return checkedStateList(context, R.color.white); 36 | } 37 | 38 | public ColorStateList radioButtonStateList(Context context) { 39 | return checkedStateList(context, R.color.white_20); 40 | } 41 | 42 | private ColorStateList checkedStateList(Context context, @ColorRes int defaultColorRes) { 43 | int[][] states = new int[][]{ 44 | new int[]{android.R.attr.state_checked}, 45 | new int[]{-android.R.attr.state_checked} 46 | }; 47 | 48 | int[] colors = new int[]{ 49 | color(context), 50 | ContextCompat.getColor(context, defaultColorRes) 51 | }; 52 | 53 | return new ColorStateList(states, colors); 54 | } 55 | 56 | public ColorStateList colorStateListFocused(Context context) { 57 | int[][] states = new int[][]{ 58 | new int[]{android.R.attr.state_enabled, android.R.attr.state_focused}, 59 | new int[]{} 60 | }; 61 | 62 | int[] colors = new int[]{ 63 | color(context), 64 | ContextCompat.getColor(context, R.color.gray_controls) 65 | }; 66 | 67 | return new ColorStateList(states, colors); 68 | } 69 | 70 | public ColorStateList primaryColorAsStateList(Context context) { 71 | return ColorStateList.valueOf(color(context)); 72 | } 73 | 74 | public int color(Context context) { 75 | return ContextCompat.getColor(context, primaryColorRes); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/src/main/java/com/chess/clock/entities/ClockTime.java: -------------------------------------------------------------------------------- 1 | package com.chess.clock.entities; 2 | 3 | import android.annotation.SuppressLint; 4 | 5 | public final class ClockTime { 6 | public static int HOUR_MILLIS = 3600000; 7 | public static String CLOCK_FORMAT_HOURS = "%d:%02d:%02d"; 8 | public static String CLOCK_FORMAT_MINUTES = "%d:%02d"; 9 | public final int hours; 10 | public final int minutes; 11 | public final int seconds; 12 | public final long remainingTimeMs; 13 | 14 | private ClockTime(long timeMs) { 15 | remainingTimeMs = timeMs; 16 | seconds = (int) (timeMs / 1000) % 60; 17 | minutes = (int) ((timeMs / (1000 * 60)) % 60); 18 | hours = (int) ((timeMs / (1000 * 60 * 60))); 19 | } 20 | 21 | public static boolean atLeastHourLeft(long timeMs) { 22 | return timeMs >= HOUR_MILLIS; 23 | } 24 | 25 | public static String calibratedReadableFormat(long timeMs) { 26 | long calibratedTime = calibrateTime(timeMs); 27 | if (atLeastHourLeft(calibratedTime)) { 28 | return String.format( 29 | CLOCK_FORMAT_HOURS, 30 | (int) ((calibratedTime / (1000 * 60 * 60))), 31 | (int) ((calibratedTime / (1000 * 60)) % 60), 32 | (int) (calibratedTime / 1000) % 60 33 | ); 34 | } else { 35 | return String.format( 36 | CLOCK_FORMAT_MINUTES, 37 | (int) ((calibratedTime / (1000 * 60)) % 60), 38 | (int) (calibratedTime / 1000) % 60 39 | ); 40 | } 41 | } 42 | 43 | public static ClockTime calibrated(long timeMs) { 44 | // calibration 45 | long remainingTime = calibrateTime(timeMs); 46 | return new ClockTime(remainingTime); 47 | } 48 | 49 | private static long calibrateTime(long timeMs) { 50 | int rest = (int) (timeMs % 1000); 51 | if (rest > 0 && timeMs > 0) { 52 | return timeMs + 1000; 53 | } 54 | return timeMs; 55 | } 56 | 57 | public static ClockTime raw(long timeMs) { 58 | return new ClockTime(timeMs); 59 | } 60 | 61 | @SuppressLint("DefaultLocale") 62 | public String toReadableFormat() { 63 | if (atLeastOneHourLeft()) { 64 | return String.format(CLOCK_FORMAT_HOURS, hours, minutes, seconds); 65 | } else { 66 | return String.format(CLOCK_FORMAT_MINUTES, minutes, seconds); 67 | } 68 | } 69 | 70 | public int totalMinutes() { 71 | return hours * 60 + minutes; 72 | } 73 | 74 | public String toMinutesFormat() { 75 | return String.format(CLOCK_FORMAT_MINUTES, totalMinutes(), seconds); 76 | } 77 | 78 | public boolean atLeastOneHourLeft() { 79 | return atLeastHourLeft(remainingTimeMs); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /app/src/main/java/com/chess/clock/fragments/BaseFragment.java: -------------------------------------------------------------------------------- 1 | package com.chess.clock.fragments; 2 | 3 | import android.os.Bundle; 4 | import android.view.View; 5 | 6 | import androidx.annotation.NonNull; 7 | import androidx.annotation.Nullable; 8 | import androidx.fragment.app.Fragment; 9 | 10 | import com.chess.clock.activities.BaseActivity; 11 | import com.chess.clock.entities.AppTheme; 12 | 13 | /** 14 | * BaseFragment requires BaseActivity as a parent to load theme. 15 | */ 16 | public abstract class BaseFragment extends Fragment { 17 | AppTheme loadedTheme; 18 | boolean shouldReloadTheme = false; 19 | 20 | @Override 21 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 22 | shouldReloadTheme = true; 23 | super.onViewCreated(view, savedInstanceState); 24 | } 25 | 26 | @Override 27 | public void onResume() { 28 | super.onResume(); 29 | AppTheme activityTheme = getBaseActivity().selectedTheme; 30 | 31 | if (activityTheme == null) return; 32 | 33 | if (activityTheme != loadedTheme || shouldReloadTheme) { 34 | loadedTheme = activityTheme; 35 | loadTheme(loadedTheme); 36 | shouldReloadTheme = false; 37 | } 38 | } 39 | 40 | @NonNull 41 | private BaseActivity getBaseActivity() { 42 | return (BaseActivity) requireActivity(); 43 | } 44 | 45 | abstract void loadTheme(AppTheme theme); 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/chess/clock/manager/ChessClockManager.java: -------------------------------------------------------------------------------- 1 | package com.chess.clock.manager; 2 | 3 | import com.chess.clock.engine.ClockPlayer; 4 | import com.chess.clock.engine.CountDownTimer; 5 | import com.chess.clock.engine.TimeControlWrapper; 6 | 7 | 8 | public interface ChessClockManager { 9 | 10 | /** 11 | * Add new time controls to Chess Clock and resetTimeControl timer if needed. 12 | * 13 | * @param timeControlWrapper selected time control to set 14 | */ 15 | void setupClock(TimeControlWrapper timeControlWrapper); 16 | 17 | /** 18 | * Notifies that ClockPlayer pressed the clock. 19 | */ 20 | void pressClock(ClockPlayer player); 21 | 22 | /** 23 | * Pauses the global state of the chess clock. 24 | */ 25 | void pauseClock(); 26 | 27 | /** 28 | * @return current time for selected player in milliseconds 29 | */ 30 | long getTimeForPlayer(ClockPlayer player); 31 | 32 | /** 33 | * @return true if any countdown timer is running 34 | */ 35 | boolean isClockStarted(); 36 | 37 | /** 38 | * Registers a callbacks to be invoked on players statuses updates. 39 | */ 40 | void setListeners(CountDownTimer.Callback playerOneCallback, CountDownTimer.Callback playerTwoCallback); 41 | 42 | /** 43 | * Resets the timer and time control state of both players. 44 | */ 45 | void resetClock(); 46 | 47 | /** 48 | * Set selected player time regardless of the current settings 49 | */ 50 | void setPlayerTime(ClockPlayer ofBoolean, long timeMs); 51 | } 52 | 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/chess/clock/manager/ChessClockManagerImpl.java: -------------------------------------------------------------------------------- 1 | package com.chess.clock.manager; 2 | 3 | import android.util.Log; 4 | 5 | import com.chess.clock.engine.ClockPlayer; 6 | import com.chess.clock.engine.CountDownTimer; 7 | import com.chess.clock.engine.TimeControlWrapper; 8 | import com.chess.clock.entities.ClockTime; 9 | import com.chess.clock.util.Args; 10 | 11 | public class ChessClockManagerImpl implements ChessClockManager { 12 | 13 | /** 14 | * Logs stuff 15 | */ 16 | private static final String TAG = ChessClockManager.class.getName(); 17 | 18 | /** 19 | * True if the game is on-going. Note: pause state still counts as game running. 20 | */ 21 | private boolean chessGameRunning; 22 | 23 | /** 24 | * Count down timers for both players. 25 | */ 26 | private final CountDownTimer mPlayerOneTimer; 27 | private final CountDownTimer mPlayerTwoTimer; 28 | 29 | 30 | public ChessClockManagerImpl() { 31 | chessGameRunning = false; 32 | 33 | long DEFAULT_COUNT_DOWN_INTERVAL_MS = 100; 34 | mPlayerOneTimer = new CountDownTimer(DEFAULT_COUNT_DOWN_INTERVAL_MS); 35 | mPlayerTwoTimer = new CountDownTimer(DEFAULT_COUNT_DOWN_INTERVAL_MS); 36 | 37 | CountDownTimer.FinishCallback mFinishListener = () -> { 38 | 39 | chessGameRunning = false; 40 | mPlayerOneTimer.stop(); 41 | mPlayerTwoTimer.stop(); 42 | 43 | log("Game finished."); 44 | }; 45 | mPlayerOneTimer.setFinishListener(mFinishListener); 46 | mPlayerTwoTimer.setFinishListener(mFinishListener); 47 | } 48 | 49 | @Override 50 | public void setupClock(TimeControlWrapper timeControlWrapper) { 51 | Args.checkForNull(timeControlWrapper); 52 | 53 | // Finish running game 54 | if (chessGameRunning) { 55 | log("Finishing current timers."); 56 | // Reset the clock to new time controls 57 | resetClock(); 58 | chessGameRunning = false; 59 | } 60 | 61 | mPlayerOneTimer.setTimeControl(timeControlWrapper.getTimeControlPlayerOne()); 62 | mPlayerTwoTimer.setTimeControl(timeControlWrapper.getTimeControlPlayerTwo()); 63 | 64 | log("Time Control set."); 65 | } 66 | 67 | 68 | @Override 69 | public void pressClock(ClockPlayer player) { 70 | switch (player) { 71 | case ONE: 72 | log("(1) pressed the clock."); 73 | pressPlayerClock(mPlayerOneTimer, mPlayerTwoTimer); 74 | break; 75 | case TWO: 76 | log("(2) pressed the clock."); 77 | pressPlayerClock(mPlayerTwoTimer, mPlayerOneTimer); 78 | break; 79 | } 80 | } 81 | 82 | @Override 83 | public void pauseClock() { 84 | // The pause instruction only affects the time control which has 85 | // the count down timer in the state CountDownTimer.TimerState.RUNNING. 86 | if (mPlayerOneTimer != null && mPlayerTwoTimer != null) { 87 | 88 | mPlayerOneTimer.pause(); 89 | mPlayerTwoTimer.pause(); 90 | 91 | log("Paused the clock timers."); 92 | } 93 | } 94 | 95 | @Override 96 | public void resetClock() { 97 | mPlayerOneTimer.resetTimeControl(); 98 | mPlayerTwoTimer.resetTimeControl(); 99 | chessGameRunning = false; 100 | } 101 | 102 | @Override 103 | public long getTimeForPlayer(ClockPlayer player) { 104 | long time = 0L; 105 | switch (player) { 106 | case ONE: 107 | time = mPlayerOneTimer.getTime(); 108 | break; 109 | case TWO: 110 | time = mPlayerTwoTimer.getTime(); 111 | break; 112 | } 113 | return time; 114 | } 115 | 116 | @Override 117 | public void setPlayerTime(ClockPlayer player, long timeMs) { 118 | switch (player) { 119 | case ONE: 120 | mPlayerOneTimer.setTime(timeMs); 121 | break; 122 | case TWO: 123 | mPlayerTwoTimer.setTime(timeMs); 124 | break; 125 | } 126 | } 127 | 128 | 129 | /** 130 | * Notifies the Chess Clock Service that a player made a move. 131 | * 132 | * @param playerTimer The player timer which made a move. 133 | * @param opponentTimer The opponent timer. 134 | */ 135 | private void pressPlayerClock(CountDownTimer playerTimer, CountDownTimer opponentTimer) { 136 | 137 | // Ignore clock press if timers are not initiated or opponent timer already finished. 138 | if (opponentTimer.isNotFinished() && playerTimer.isNotFinished()) { 139 | 140 | // Game already running? stop player timer and start the opponent timer. 141 | if (chessGameRunning) { 142 | playerTimer.stop(); 143 | opponentTimer.start(); 144 | 145 | log("Move number: " + playerTimer.getTotalMoveCount() + ", time left: " + ClockTime.raw(playerTimer.getTime()).toReadableFormat()); 146 | } 147 | // First move: do not stop player clock to avoid invalid initial time increment. 148 | else { 149 | opponentTimer.start(); 150 | chessGameRunning = true; 151 | 152 | log("Game started."); 153 | } 154 | } else { 155 | log("Discarded clock press due to Time Controls not available or game finished already."); 156 | } 157 | } 158 | 159 | @Override 160 | public boolean isClockStarted() { 161 | return mPlayerOneTimer.isStarted() || mPlayerTwoTimer.isStarted(); 162 | } 163 | 164 | @Override 165 | public void setListeners(CountDownTimer.Callback playerOneCallback, CountDownTimer.Callback playerTwoCallback) { 166 | if (playerOneCallback != null) { 167 | log("(1)registered listener: #" + playerOneCallback.hashCode()); 168 | } 169 | mPlayerOneTimer.setClockTimerListener(playerOneCallback); 170 | 171 | if (playerTwoCallback != null) { 172 | log("(2) registered listener: #" + playerTwoCallback.hashCode()); 173 | } 174 | mPlayerTwoTimer.setClockTimerListener(playerTwoCallback); 175 | } 176 | 177 | private void log(String message) { 178 | Log.d(TAG, message); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /app/src/main/java/com/chess/clock/statics/AppConstants.java: -------------------------------------------------------------------------------- 1 | package com.chess.clock.statics; 2 | 3 | /** 4 | * Created with Android Studio. 5 | * User: pedroteixeira pmcteixeira@gmail.com 6 | * Date: 17/09/14 7 | * Time: 14:19 8 | */ 9 | public class AppConstants { 10 | public static final String PREF_CLOCK_FULL_SCREEN = "clock_full_screen"; 11 | public static final String PREF_CLOCK_SOUNDS_ON = "clock_sounds_on"; 12 | public static final String PREF_THEME = "theme"; 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/chess/clock/statics/AppData.java: -------------------------------------------------------------------------------- 1 | package com.chess.clock.statics; 2 | 3 | import static com.chess.clock.statics.AppConstants.PREF_THEME; 4 | 5 | import android.content.Context; 6 | import android.content.SharedPreferences; 7 | 8 | import com.chess.clock.entities.AppTheme; 9 | 10 | /** 11 | * Created with Android Studio. 12 | * User: pedroteixeira pmcteixeira@gmail.com 13 | * Date: 17/09/14 14 | * Time: 14:15 15 | */ 16 | public class AppData { 17 | 18 | private final SharedPreferences preferences; 19 | 20 | public AppData(Context context) { 21 | this.preferences = context.getSharedPreferences(StaticData.SHARED_DATA_NAME, Context.MODE_PRIVATE); 22 | } 23 | 24 | public boolean getClockFullScreen() { 25 | return preferences.getBoolean(AppConstants.PREF_CLOCK_FULL_SCREEN, true); 26 | } 27 | 28 | public boolean areSoundsEnabled() { 29 | return preferences.getBoolean(AppConstants.PREF_CLOCK_SOUNDS_ON, true); 30 | } 31 | 32 | public void setSoundsEnabled(Boolean enabled) { 33 | preferences.edit().putBoolean(AppConstants.PREF_CLOCK_SOUNDS_ON, enabled).apply(); 34 | } 35 | 36 | public AppTheme getSelectedTheme() { 37 | int themeOrdinal = preferences.getInt(PREF_THEME, AppTheme.GREEN.ordinal()); 38 | return AppTheme.fromInt(themeOrdinal); 39 | } 40 | 41 | public void saveAppSetup(AppTheme theme, boolean fullScreen, boolean soundsEnabled) { 42 | preferences.edit() 43 | .putInt(PREF_THEME, theme.ordinal()) 44 | .putBoolean(AppConstants.PREF_CLOCK_SOUNDS_ON, soundsEnabled) 45 | .putBoolean(AppConstants.PREF_CLOCK_FULL_SCREEN, fullScreen) 46 | .apply(); 47 | } 48 | } -------------------------------------------------------------------------------- /app/src/main/java/com/chess/clock/statics/StaticData.java: -------------------------------------------------------------------------------- 1 | package com.chess.clock.statics; 2 | 3 | /** 4 | * Created with Android Studio. 5 | * User: pedroteixeira pmcteixeira@gmail.com 6 | * Date: 17/09/14 7 | * Time: 14:16 8 | */ 9 | public class StaticData { 10 | 11 | public static final String SHARED_DATA_NAME = "sharedData"; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/chess/clock/util/Args.java: -------------------------------------------------------------------------------- 1 | package com.chess.clock.util; 2 | 3 | /** 4 | *

5 | * This class has utility methods for common argument validations. Replaces if statements 6 | * at the forceStart of a method with more compact method calls. 7 | *

8 | *

9 | * Example use case. Instead of: 10 | *

11 |  *        public void doThis(String aText) {
12 |  *            if (!Util.textHasContent(aText)) {
13 |  *                throw new IllegalArgumentException();
14 |  *            }
15 |  *            //..main body elided
16 |  *        }
17 |  *        
18 | *

19 | *

20 | *

21 | * One may instead write: 22 | *

23 | *

24 |  *        public void doThis(String aText) {
25 |  *             Args.checkForContent(aText);
26 |  *             //..main body elided
27 |  *        }
28 |  *        
29 | *

30 | *

31 | */ 32 | public final class Args { 33 | 34 | /** 35 | * If aText does not satisfy {@link Args#textHasContent}, then throw an 36 | * IllegalArgumentException. Most text used in an application is meaningful only 37 | * if it has visible content. 38 | */ 39 | public static void checkForContent(String aText) { 40 | if (!textHasContent(aText)) { 41 | throw new IllegalArgumentException("Text has no visible content"); 42 | } 43 | } 44 | 45 | /** 46 | * If aNumber is less than 1, then throw an IllegalArgumentException. 47 | */ 48 | public static void checkForPositive(long aNumber) { 49 | if (aNumber < 1) { 50 | throw new IllegalArgumentException(aNumber + " is less than 1"); 51 | } 52 | } 53 | 54 | /** 55 | * If aNumber is less than 0, then throw an IllegalArgumentException. 56 | */ 57 | public static void checkForZeroOrNegative(long aNumber) { 58 | if (aNumber < 0) { 59 | throw new IllegalArgumentException(aNumber + " is less than 0"); 60 | } 61 | } 62 | 63 | /** 64 | * Returns true if aText is non-null and has visible content. This is a test which is often 65 | * performed, and should probably be placed in a general utility class. 66 | */ 67 | public static boolean textHasContent(String aText) { 68 | String EMPTY_STRING = ""; 69 | return (aText != null) && (!aText.trim().equals(EMPTY_STRING)); 70 | } 71 | 72 | /** 73 | * If aObject is null, then throw a NullPointerException. 74 | */ 75 | public static void checkForNull(Object aObject) { 76 | if (aObject == null) { 77 | throw new NullPointerException(); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /app/src/main/java/com/chess/clock/util/AutoNameFormatter.java: -------------------------------------------------------------------------------- 1 | package com.chess.clock.util; 2 | 3 | import androidx.annotation.Nullable; 4 | 5 | /** 6 | * SCHEME: “GAME_MIN(ifExist) GAME_SEC(ifExist) | INCREMENT_MIN(ifExist) INCREMENT_SEC(ifExist)” 7 | *

8 | * If there is no game time(only increment), the result should be null. 9 | * You need to provide a formatter for minutes and seconds depending on your needs. 10 | */ 11 | public class AutoNameFormatter { 12 | 13 | private final NameParametersFormat paramsFormat; 14 | 15 | public AutoNameFormatter(NameParametersFormat format) { 16 | this.paramsFormat = format; 17 | } 18 | 19 | @Nullable 20 | public String prepareAutoName( 21 | int minutes, 22 | int seconds, 23 | int incrementMinutes, 24 | int incrementSeconds) { 25 | if (minutes == 0 && seconds == 0) return null; 26 | 27 | StringBuilder builder = new StringBuilder(); 28 | if (minutes > 0) { 29 | builder.append(paramsFormat.getMinutesFormatted(minutes)); 30 | } 31 | if (seconds > 0) { 32 | builder.append(" "); 33 | builder.append(paramsFormat.getSecondsFormatted(seconds)); 34 | } 35 | if (incrementMinutes > 0 || incrementSeconds > 0) { 36 | builder.append(" |"); 37 | } 38 | if (incrementMinutes > 0) { 39 | builder.append(" "); 40 | builder.append(paramsFormat.getMinutesFormatted(incrementMinutes)); 41 | } 42 | if (incrementSeconds > 0) { 43 | builder.append(" "); 44 | builder.append(paramsFormat.getSecondsFormatted(incrementSeconds)); 45 | } 46 | 47 | return builder.toString().trim(); 48 | } 49 | 50 | public interface NameParametersFormat { 51 | String getMinutesFormatted(int minutes); 52 | 53 | String getSecondsFormatted(int seconds); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/com/chess/clock/util/ClockUtils.java: -------------------------------------------------------------------------------- 1 | package com.chess.clock.util; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.text.Editable; 5 | import android.text.TextWatcher; 6 | import android.util.Log; 7 | import android.view.inputmethod.EditorInfo; 8 | import android.widget.EditText; 9 | 10 | public class ClockUtils { 11 | @SuppressLint("DefaultLocale") 12 | public static String twoDecimalPlacesFormat(int value) { 13 | return String.format("%02d", value); 14 | } 15 | 16 | public static int getIntOrZero(EditText et) { 17 | int intValue = 0; 18 | String textValue = et.getText().toString(); 19 | try { 20 | intValue = textValue.isEmpty() ? 0 : Integer.parseInt(textValue); 21 | } catch (NumberFormatException exception) { 22 | Log.i("ClockUtils", "Cannot parse value:" + textValue + " to Integer."); 23 | } 24 | return intValue; 25 | } 26 | 27 | public static void setClockTextWatcher(EditText editText) { 28 | setClockTextWatcherWithCallback(editText, () -> { 29 | // no-op 30 | }); 31 | } 32 | 33 | public static void setClockTextWatcherWithCallback(EditText editText, OnTimeEditCallback callback) { 34 | TextWatcher minutesTextWatcher = new TextWatcher() { 35 | final int MAX = 59; 36 | 37 | @Override 38 | public void beforeTextChanged(CharSequence s, int start, int count, int after) { 39 | } 40 | 41 | @Override 42 | public void onTextChanged(CharSequence s, int start, int before, int count) { 43 | } 44 | 45 | @Override 46 | public void afterTextChanged(Editable s) { 47 | editText.removeTextChangedListener(this); 48 | String minutesAsString = s.toString(); 49 | int minutes = minutesAsString.isEmpty() ? 0 : Integer.parseInt(minutesAsString); 50 | if (minutes > MAX) { 51 | s.clear(); 52 | s.append(twoDecimalPlacesFormat(MAX)); 53 | } 54 | editText.addTextChangedListener(this); 55 | callback.onTimeChange(); 56 | } 57 | }; 58 | editText.addTextChangedListener(minutesTextWatcher); 59 | editText.setOnEditorActionListener((v, actionId, event) -> { 60 | if (actionId == EditorInfo.IME_ACTION_NEXT || actionId == EditorInfo.IME_ACTION_DONE) { 61 | String minutesAsString = v.getText().toString(); 62 | int minutes = minutesAsString.isEmpty() ? 0 : Integer.parseInt(minutesAsString); 63 | v.setText(twoDecimalPlacesFormat(minutes)); 64 | v.clearFocus(); 65 | } 66 | return false; 67 | }); 68 | } 69 | 70 | public static void clearFocusOnActionDone(EditText editText) { 71 | editText.setOnEditorActionListener((v, actionId, event) -> { 72 | if (actionId == EditorInfo.IME_ACTION_DONE) { 73 | v.clearFocus(); 74 | } 75 | return false; 76 | }); 77 | } 78 | 79 | public static void onTimeChangedAction(EditText editText, OnTimeEditCallback callback) { 80 | TextWatcher textWatcher = new TextWatcher() { 81 | 82 | @Override 83 | public void beforeTextChanged(CharSequence s, int start, int count, int after) { 84 | } 85 | 86 | @Override 87 | public void onTextChanged(CharSequence s, int start, int before, int count) { 88 | } 89 | 90 | @Override 91 | public void afterTextChanged(Editable s) { 92 | callback.onTimeChange(); 93 | } 94 | }; 95 | editText.addTextChangedListener(textWatcher); 96 | } 97 | 98 | 99 | public static long durationMillis(int hours, int minutes, int seconds) { 100 | return hours * 60 * 60 * 1000L + minutes * 60 * 1000L + seconds * 1000L; 101 | } 102 | 103 | public interface OnTimeEditCallback { 104 | void onTimeChange(); 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /app/src/main/java/com/chess/clock/views/ClockButton.java: -------------------------------------------------------------------------------- 1 | package com.chess.clock.views; 2 | 3 | import static com.chess.clock.views.ViewUtils.showView; 4 | 5 | import android.annotation.SuppressLint; 6 | import android.content.Context; 7 | import android.util.AttributeSet; 8 | import android.util.TypedValue; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.widget.FrameLayout; 12 | import android.widget.TextView; 13 | 14 | import androidx.annotation.Keep; 15 | import androidx.core.content.ContextCompat; 16 | 17 | import com.chess.clock.R; 18 | import com.chess.clock.entities.AppTheme; 19 | import com.chess.clock.entities.ClockTime; 20 | 21 | @Keep 22 | public class ClockButton extends FrameLayout { 23 | 24 | private final int idleTextColor; 25 | private final int runningTextColor; 26 | private final TextView timeTv; 27 | private final TextView movesTv; 28 | private final TextView controlNameTv; 29 | private final View timeOptions; 30 | private final View stageOne; 31 | private final View stageTwo; 32 | private final View stageThree; 33 | int idleBgColor = ContextCompat.getColor(getContext(), R.color.gray_light); 34 | 35 | public ClockButton(Context context, AttributeSet attrs) { 36 | super(context, attrs); 37 | LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 38 | View view = inflater.inflate(R.layout.view_clock_button, this, true); 39 | timeTv = view.findViewById(R.id.clockTimeTv); 40 | movesTv = view.findViewById(R.id.movesTv); 41 | stageOne = view.findViewById(R.id.stageOne); 42 | stageTwo = view.findViewById(R.id.stageTwo); 43 | stageThree = view.findViewById(R.id.stageThree); 44 | controlNameTv = view.findViewById(R.id.stageNameTv); 45 | timeOptions = view.findViewById(R.id.adjustTimeImg); 46 | 47 | setForeground(ViewUtils.getSelectableItemBgDrawable(getContext())); 48 | 49 | idleTextColor = getResources().getColor(R.color.black_70); 50 | runningTextColor = getResources().getColor(R.color.white); 51 | } 52 | 53 | 54 | public void setTime(long timeMillis) { 55 | String readableTime = ClockTime.calibratedReadableFormat(timeMillis); 56 | 57 | if (timeTv.getText().equals(readableTime)) return; 58 | 59 | timeTv.setText(readableTime); 60 | if (ClockTime.atLeastHourLeft(timeMillis)) { 61 | timeTv.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension(R.dimen.clock_timer_textSize_small)); 62 | } else { 63 | timeTv.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension(R.dimen.clock_timer_textSize_normal)); 64 | } 65 | } 66 | 67 | @SuppressLint("DefaultLocale") 68 | public void setMoves(int moves) { 69 | movesTv.setText(getContext().getString(R.string.moves_x, moves)); 70 | } 71 | 72 | public void setClockButtonClickListener(ClockClickListener listener) { 73 | setOnClickListener(v -> listener.onClickClock()); 74 | timeOptions.setOnClickListener(v -> listener.onClickOptions()); 75 | } 76 | 77 | public void updateUi( 78 | AppTheme theme, 79 | State state 80 | ) { 81 | switch (state) { 82 | case IDLE: 83 | case LOCKED: 84 | setBackgroundColor(idleBgColor); 85 | timeTv.setTextColor(idleTextColor); 86 | break; 87 | case RUNNING: 88 | setBackgroundColor(ContextCompat.getColor(getContext(), theme.primaryColorRes)); 89 | timeTv.setTextColor(runningTextColor); 90 | break; 91 | case FINISHED: 92 | setBackgroundColor(ContextCompat.getColor(getContext(), R.color.red)); 93 | timeTv.setTextColor(idleTextColor); 94 | break; 95 | } 96 | setClickable(state != State.LOCKED); 97 | boolean hideStageControls = state != State.IDLE; 98 | ViewUtils.isInvisible(timeOptions, hideStageControls); 99 | ViewUtils.isInvisible(controlNameTv, hideStageControls); 100 | } 101 | 102 | private void setStageBg(View stage, Boolean active) { 103 | if (active) { 104 | stage.setBackgroundResource(R.drawable.shape_stage_fill); 105 | } else { 106 | stage.setBackgroundResource(R.drawable.shape_stage_empty); 107 | } 108 | } 109 | 110 | public void updateStage(int stageId, String timeControlName) { 111 | //stage one is always filled if visible 112 | setStageBg(stageTwo, stageId > 0); 113 | setStageBg(stageThree, stageId > 1); 114 | controlNameTv.setText(timeControlName); 115 | } 116 | 117 | public void setStages(int stagesNumber) { 118 | // no stages indicators visible for 1 stage game 119 | showView(stageOne, stagesNumber > 1); 120 | showView(stageTwo, stagesNumber > 1); 121 | showView(stageThree, stagesNumber > 2); 122 | } 123 | 124 | public enum State { 125 | IDLE, LOCKED, RUNNING, FINISHED 126 | } 127 | 128 | @Keep 129 | public interface ClockClickListener { 130 | void onClickClock(); 131 | 132 | void onClickOptions(); 133 | } 134 | } -------------------------------------------------------------------------------- /app/src/main/java/com/chess/clock/views/ClockMenu.java: -------------------------------------------------------------------------------- 1 | package com.chess.clock.views; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.widget.ImageView; 8 | 9 | import androidx.constraintlayout.widget.ConstraintLayout; 10 | 11 | import com.chess.clock.R; 12 | 13 | public class ClockMenu extends ConstraintLayout { 14 | 15 | private final ImageView settingsButton; 16 | private final ImageView playPauseButton; 17 | private final ImageView resetButton; 18 | private final ImageView soundButton; 19 | 20 | public ClockMenu(Context context, AttributeSet attrs) { 21 | super(context, attrs); 22 | LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 23 | View view = inflater.inflate(R.layout.view_clock_menu, this, true); 24 | view.setBackgroundResource(R.color.gray_controls); 25 | 26 | settingsButton = view.findViewById(R.id.settingsBtn); 27 | playPauseButton = view.findViewById(R.id.playPauseBtn); 28 | resetButton = view.findViewById(R.id.resetBtn); 29 | soundButton = view.findViewById(R.id.soundBtn); 30 | } 31 | 32 | public void setListener(MenuClickListener listener) { 33 | settingsButton.setOnClickListener(v -> listener.timeSettingsClicked()); 34 | playPauseButton.setOnClickListener(v -> listener.playPauseClicked()); 35 | resetButton.setOnClickListener(v -> listener.resetClicked()); 36 | soundButton.setOnClickListener(v -> listener.soundClicked()); 37 | } 38 | 39 | public void showPause() { 40 | playPauseButton.setImageResource(R.drawable.ic_pause); 41 | playPauseButton.setVisibility(View.VISIBLE); 42 | } 43 | 44 | public void showPlay() { 45 | playPauseButton.setImageResource(R.drawable.ic_play); 46 | playPauseButton.setVisibility(View.VISIBLE); 47 | } 48 | 49 | public void hidePlayPauseBtn() { 50 | playPauseButton.setVisibility(View.INVISIBLE); 51 | } 52 | 53 | public void updateSoundIcon(boolean soundsEnabled) { 54 | if (soundsEnabled) { 55 | soundButton.setImageResource(R.drawable.ic_sound); 56 | } else { 57 | soundButton.setImageResource(R.drawable.ic_sound_off); 58 | } 59 | } 60 | 61 | public interface MenuClickListener { 62 | void timeSettingsClicked(); 63 | 64 | void playPauseClicked(); 65 | 66 | void resetClicked(); 67 | 68 | void soundClicked(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /app/src/main/java/com/chess/clock/views/StageRowView.java: -------------------------------------------------------------------------------- 1 | package com.chess.clock.views; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.widget.TextView; 8 | 9 | import androidx.annotation.NonNull; 10 | import androidx.annotation.Nullable; 11 | import androidx.constraintlayout.widget.ConstraintLayout; 12 | 13 | import com.chess.clock.R; 14 | import com.chess.clock.engine.Stage; 15 | 16 | public class StageRowView extends ConstraintLayout { 17 | 18 | TextView stageDetails; 19 | TextView timeIncrementDetails; 20 | TextView positionLabel; 21 | 22 | public StageRowView(@NonNull Context context, @Nullable AttributeSet attrs) { 23 | super(context, attrs); 24 | 25 | LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 26 | View view = inflater.inflate(R.layout.list_stage_item, this, true); 27 | 28 | positionLabel = view.findViewById(R.id.positionTv); 29 | stageDetails = view.findViewById(R.id.stageDetailsTv); 30 | timeIncrementDetails = view.findViewById(R.id.incrementDetailsTv); 31 | } 32 | 33 | public void updateData(int position, Stage stage) { 34 | positionLabel.setText(String.valueOf(position)); 35 | String details = ""; 36 | String timeFormatted = stage.durationTimeFormatted(); 37 | if (stage.getStageType() == Stage.StageType.GAME) { 38 | details = getContext().getString(R.string.game_in_x, timeFormatted); 39 | } else { 40 | details = getResources().getQuantityString(R.plurals.x_moves_in, stage.getTotalMoves(), stage.getTotalMoves(), timeFormatted); 41 | } 42 | stageDetails.setText(details); 43 | timeIncrementDetails.setText(stage.getTimeIncrement().toString()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/com/chess/clock/views/StyledButton.java: -------------------------------------------------------------------------------- 1 | package com.chess.clock.views; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.util.AttributeSet; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.widget.FrameLayout; 9 | import android.widget.TextView; 10 | 11 | import androidx.cardview.widget.CardView; 12 | 13 | import com.chess.clock.R; 14 | 15 | public class StyledButton extends FrameLayout { 16 | 17 | private final CardView topLayer; 18 | private final CardView bottomLayer; 19 | 20 | public StyledButton(Context context, AttributeSet attrs) { 21 | super(context, attrs); 22 | 23 | LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 24 | View view = inflater.inflate(R.layout.view_styled_button, this, true); 25 | TextView text = view.findViewById(R.id.text); 26 | 27 | topLayer = view.findViewById(R.id.topCard); 28 | bottomLayer = view.findViewById(R.id.bottomCard); 29 | 30 | if (attrs != null) { 31 | TypedArray t = context.obtainStyledAttributes(attrs, R.styleable.StyledButton, 0, 0); 32 | String buttonText = t.getString(R.styleable.StyledButton_android_text); 33 | t.recycle(); 34 | text.setText(buttonText); 35 | } 36 | } 37 | 38 | public void setButtonBackground(int color) { 39 | topLayer.setCardBackgroundColor(color); 40 | bottomLayer.setCardBackgroundColor(color); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/chess/clock/views/ViewUtils.java: -------------------------------------------------------------------------------- 1 | package com.chess.clock.views; 2 | 3 | import android.app.Dialog; 4 | import android.content.Context; 5 | import android.content.res.Resources; 6 | import android.content.res.TypedArray; 7 | import android.graphics.drawable.Drawable; 8 | import android.util.TypedValue; 9 | import android.view.View; 10 | import android.widget.TextView; 11 | 12 | import androidx.core.content.ContextCompat; 13 | 14 | import com.chess.clock.R; 15 | 16 | public class ViewUtils { 17 | public static void showView(View v, Boolean show) { 18 | if (show) { 19 | v.setVisibility(View.VISIBLE); 20 | } else { 21 | v.setVisibility(View.GONE); 22 | } 23 | } 24 | 25 | public static void isInvisible(View v, boolean invisible) { 26 | if (invisible) { 27 | v.setVisibility(View.INVISIBLE); 28 | } else { 29 | v.setVisibility(View.VISIBLE); 30 | } 31 | } 32 | 33 | /** 34 | * call before dialog `show()` 35 | */ 36 | public static void setLargePopupMessageTextSize(Dialog dialog, Resources resources) { 37 | dialog.setOnShowListener(dialogInterface -> { 38 | TextView messageTv = dialog.findViewById(android.R.id.message); 39 | if (messageTv != null) { 40 | messageTv.setTextSize(TypedValue.COMPLEX_UNIT_PX, resources.getDimension(R.dimen.text_size_dialog_title)); 41 | } 42 | }); 43 | } 44 | 45 | public static Drawable getSelectableItemBgDrawable(Context context) { 46 | TypedArray a = context.getTheme().obtainStyledAttributes(R.style.AppTheme, new int[]{android.R.attr.selectableItemBackground}); 47 | int attributeResourceId = a.getResourceId(0, 0); 48 | return ContextCompat.getDrawable(context, attributeResourceId); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/res/anim/left_to_right_full.xml: -------------------------------------------------------------------------------- 1 | 3 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/anim/left_to_right_in.xml: -------------------------------------------------------------------------------- 1 | 3 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/anim/right_to_left_full.xml: -------------------------------------------------------------------------------- 1 | 3 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/anim/right_to_left_out.xml: -------------------------------------------------------------------------------- 1 | 3 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/bg_green_pressed.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/add_time_button_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_btn_clock_running.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_green_pressed.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_popup.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_white_50_rounded.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_bg_selector_green.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_right_24.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_back_arrow_white.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_check_box.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_check_box_frame.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_checkmark.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_checkmark_white.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_delete.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_edit.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_edit_pencil.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_edit_time.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_edit_time_small.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_fullscreen.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_fullscreen_exit.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 13 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 13 | 16 | 19 | 22 | 25 | 28 | 31 | 34 | 37 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_logo.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 14 | 18 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_options.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_pause.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_play.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_plus_green.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_radio_frame.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_radio_selected.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_refresh.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_reorder_icon.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings_sound_off.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings_sound_on.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_sound.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_sound_off.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/list_radio_button_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_stage_empty.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_stage_fill.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/font/montserrat_extra_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChessCom/android-chessclock/9e5862c9ce2caa28f42ad53009a3cb8b7a93decf/app/src/main/res/font/montserrat_extra_bold.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/roboto_medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChessCom/android-chessclock/9e5862c9ce2caa28f42ad53009a3cb8b7a93decf/app/src/main/res/font/roboto_medium.ttf -------------------------------------------------------------------------------- /app/src/main/res/layout-land/activity_clock_timers.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 21 | 22 | 31 | 32 | 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/res/layout-land/activity_clock_timers_reversed.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 21 | 22 | 31 | 32 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/res/layout-land/view_clock_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 20 | 21 | 33 | 34 | 35 | 47 | 48 | 60 | 61 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_clock_timers.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | 18 | 19 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_timer_settings.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_fragment_edit_time_increment.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 23 | 24 | 35 | 36 | 37 | 45 | 46 | 59 | 60 | 73 | 74 | 87 | 88 | 101 | 102 | 111 | 112 | 113 | 121 | 122 | 130 | 131 | 141 | 142 | 146 | 147 | 154 | 155 | 156 | 157 | 158 | 162 | 163 | 169 | 170 | 171 | 172 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_title_text_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 16 | 23 | 24 | 32 | 33 | 42 | 43 | 44 | 45 | 46 | 55 | 56 | 64 | 65 | 72 | 73 | 81 | 82 | 83 | 84 | 85 | 92 | 93 | 94 | 95 | 102 | 103 | 104 | 105 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_theme.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 18 | 19 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_view_time.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 21 | 22 | 40 | 41 | 54 | 55 | 69 | -------------------------------------------------------------------------------- /app/src/main/res/layout/list_stage_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 23 | 24 | 36 | 37 | 49 | 50 | 59 | 60 | -------------------------------------------------------------------------------- /app/src/main/res/layout/view_clock_button.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | 12 | 21 | 22 | 28 | 29 | 37 | 38 | 46 | 47 | 48 | 49 | 60 | 61 | 74 | 75 | 90 | 91 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /app/src/main/res/layout/view_clock_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 21 | 22 | 35 | 36 | 42 | 43 | 56 | 57 | 70 | 71 | -------------------------------------------------------------------------------- /app/src/main/res/layout/view_styled_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 25 | 26 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/menu/settings_actions.xml: -------------------------------------------------------------------------------- 1 |

3 | 4 | 5 | 10 | 11 | 12 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/menu/settings_cab_actions.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChessCom/android-chessclock/9e5862c9ce2caa28f42ad53009a3cb8b7a93decf/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChessCom/android-chessclock/9e5862c9ce2caa28f42ad53009a3cb8b7a93decf/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChessCom/android-chessclock/9e5862c9ce2caa28f42ad53009a3cb8b7a93decf/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChessCom/android-chessclock/9e5862c9ce2caa28f42ad53009a3cb8b7a93decf/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChessCom/android-chessclock/9e5862c9ce2caa28f42ad53009a3cb8b7a93decf/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChessCom/android-chessclock/9e5862c9ce2caa28f42ad53009a3cb8b7a93decf/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChessCom/android-chessclock/9e5862c9ce2caa28f42ad53009a3cb8b7a93decf/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChessCom/android-chessclock/9e5862c9ce2caa28f42ad53009a3cb8b7a93decf/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChessCom/android-chessclock/9e5862c9ce2caa28f42ad53009a3cb8b7a93decf/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChessCom/android-chessclock/9e5862c9ce2caa28f42ad53009a3cb8b7a93decf/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/raw/chess_clock_pause.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChessCom/android-chessclock/9e5862c9ce2caa28f42ad53009a3cb8b7a93decf/app/src/main/res/raw/chess_clock_pause.mp3 -------------------------------------------------------------------------------- /app/src/main/res/raw/chess_clock_reset.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChessCom/android-chessclock/9e5862c9ce2caa28f42ad53009a3cb8b7a93decf/app/src/main/res/raw/chess_clock_reset.mp3 -------------------------------------------------------------------------------- /app/src/main/res/raw/chess_clock_switch1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChessCom/android-chessclock/9e5862c9ce2caa28f42ad53009a3cb8b7a93decf/app/src/main/res/raw/chess_clock_switch1.mp3 -------------------------------------------------------------------------------- /app/src/main/res/raw/chess_clock_switch2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChessCom/android-chessclock/9e5862c9ce2caa28f42ad53009a3cb8b7a93decf/app/src/main/res/raw/chess_clock_switch2.mp3 -------------------------------------------------------------------------------- /app/src/main/res/raw/chess_clock_time_ended.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChessCom/android-chessclock/9e5862c9ce2caa28f42ad53009a3cb8b7a93decf/app/src/main/res/raw/chess_clock_time_ended.mp3 -------------------------------------------------------------------------------- /app/src/main/res/transition/fragment_in.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/values-ar/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/res/values-fr/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Horloge d\'échecs 6 | Remettre l\'horloge à zéro ? 7 | Non 8 | Oui 9 | 10 | 11 | Paramètres 12 | Modifier 13 | Supprimer 14 | Plein écran 15 | 16 | 17 | Ajouter une étape 18 | Enregistrer 19 | Veuillez ajouter un nom pour enregistrer. 20 | 21 | 22 | Liste vide. Liste par défaut chargée. 23 | Identique que pour Joueur 1 24 | 25 | 26 | Supprimer une étape ? 27 | 28 | 29 | Modifier l\'étape 30 | Annuler 31 | 32 | 33 | Délai 34 | Bronstein 35 | Fischer 36 | Aucun 37 | 38 | 39 | L\'horloge du joueur démarre après la période de retard. 40 | Les joueurs reçoivent la partie utilisée de l\'incrément à la fin de chaque tour. 41 | Les joueurs reçoivent l\'incrément complet à la fin de chaque tour. 42 | 43 | 44 | 45 | Joueur 1 46 | Joueur 2 47 | 48 | 49 | Démarrer 50 | Paramètres du temps 51 | Préréglages 52 | Nouveau minuteur personnalisé 53 | Paramètres de l\'application 54 | Couleur du thème 55 | Son 56 | Temps personnalisé 57 | %d min 58 | %d sec 59 | %d min | %2d sec 60 | Nom 61 | Temps 62 | Incrément 63 | Mode avancé 64 | Veuillez ajouter un temps de sauvegarde. 65 | Stages 66 | Etape 1 67 | Etape 2 68 | Etape 3 69 | Mouvement 70 | Valeur 71 | Type 72 | Veuillez ajouter au moins une étape pour chaque utilisateur. 73 | Delete custom time? 74 | Garder 75 | Jeu dans %s 76 | Heure 77 | Minute 78 | Seconde 79 | Sauvegarder le temps 80 | Ajuster le temps 81 | Mouvements: %d 82 | Etes-vous sûr de vouloir abandonner le nouveau minuteur personnalisé ? 83 | Restaurer les paramètres de minuteur par défaut 84 | Etes vous sûr ? Cela supprimera tous les minuteurs personnalisés que vous avez. 85 | Confirmer 86 | OK 87 | Attention 88 | L\'horloge nécessite au moins un minuteur actif. Veuillez modifier le minuteur existant ou en créer un nouveau. 89 | -------------------------------------------------------------------------------- /app/src/main/res/values-sw360dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 40sp 3 | 84sp 4 | 116sp 5 | 70dp 6 | 32dp 7 | 68dp 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values-sw600dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 100sp 4 | 144sp 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values-v19/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 15 | 16 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/values-v35/edgetoedge.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #000 5 | #33000000 6 | #66000000 7 | #A6000000 8 | #B3000000 9 | #0381b8 10 | #7fa650 11 | #A67FA650 12 | #c1a45a 13 | #e58f2a 14 | #f38272 15 | #ed3239 16 | #00000000 17 | #1bada6 18 | #fff 19 | #33FFFFFF 20 | #80ffffff 21 | #A6ffffff 22 | #D9FFFFFF 23 | #4b4847 24 | #666564 25 | #8b8987 26 | #e7e6e5 27 | 28 | 29 | #312e2b 30 | #262421 31 | 32 | 33 | #FF312e2b 34 | #6f6862 35 | #ff59534e 36 | #aa312e2b 37 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 4dp 3 | 8dp 4 | 10dp 5 | 12dp 6 | 16dp 7 | 24dp 8 | 32dp 9 | 10 | 4dp 11 | 10dp 12 | 4dp 13 | 480dp 14 | 56dp 15 | 48dp 16 | 17 | 18 | 12sp 19 | 14sp 20 | 16sp 21 | 17sp 22 | 22sp 23 | 20sp 24 | 25 | 26 | 32sp 27 | 64sp 28 | 76sp 29 | 12dp 30 | 56dp 31 | 16dp 32 | 56dp 33 | 40dp 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/values/edgetoedge.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/plurals.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d move in %s 5 | %d moves in %s 6 | 7 | 8 | 9 | %d selected 10 | %d selected 11 | %d selected 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | : 5 | 00 6 | 7 | 8 | v%s (%d) 9 | 10 | 11 | Chess Clock 12 | Reset the clock? 13 | No 14 | Yes 15 | 16 | 17 | Settings 18 | Edit 19 | Delete 20 | Full Screen 21 | 22 | 23 | Add Stage 24 | Save 25 | Please add a name to save. 26 | 27 | 28 | List empty. Default list loaded. 29 | Same as Player One 30 | 31 | 32 | Delete stage? 33 | 34 | 35 | Edit Stage 36 | Cancel 37 | 38 | 39 | Delay 40 | Bronstein 41 | Fischer 42 | None 43 | 44 | 45 | The player\'s clock starts after the delay period. 46 | Players receive the used portion of the increment at the end of each 47 | turn. 48 | 49 | Players receive the full increment at the end of each turn. 50 | 51 | 52 | Player One 53 | Player Two 54 | 55 | 56 | Start 57 | Time Controls 58 | Presets 59 | New Custom Time 60 | App Settings 61 | Theme color 62 | Sound 63 | Custom Time 64 | %d min 65 | %d sec 66 | %d min | %2d sec 67 | Name 68 | Time 69 | Increment 70 | Advanced mode 71 | Please add a time to save. 72 | Stages 73 | Stage One 74 | Stage Two 75 | Stage Three 76 | Moves 77 | Value 78 | Type 79 | Please add at least one stage for each user. 80 | Delete custom time? 81 | Keep 82 | Game in %s 83 | Hour 84 | Minute 85 | Second 86 | Save time 87 | Adjust Time 88 | Moves: %d 89 | Are you sure you want to discard new custom time? 90 | Restore Default Time Controls 91 | Are you sure? This will delete any custom time controls you have. 92 | Confirm 93 | Ok 94 | Attention 95 | The clock requires at least one active time control. Please edit the existing time control or create a new one. 96 | 97 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 17 | 18 | 23 | 24 | 29 | 30 | 38 | 39 | 42 | 43 | 56 | 57 | 70 | 71 | 82 | 83 | 89 | 90 | 95 | 96 | 100 | 101 | 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /app/src/test/java/com/chess/AutoNameFormatterTest.java: -------------------------------------------------------------------------------- 1 | package com.chess; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertNull; 5 | 6 | import com.chess.clock.util.AutoNameFormatter; 7 | 8 | import org.junit.Test; 9 | 10 | public class AutoNameFormatterTest { 11 | 12 | private final AutoNameFormatter.NameParametersFormat sampleParametersFormat = new AutoNameFormatter.NameParametersFormat() { 13 | @Override 14 | public String getMinutesFormatted(int minutes) { 15 | return minutes + " min"; 16 | } 17 | 18 | @Override 19 | public String getSecondsFormatted(int seconds) { 20 | return seconds + " sec"; 21 | } 22 | }; 23 | private final AutoNameFormatter formatter = new AutoNameFormatter(sampleParametersFormat); 24 | 25 | @Test 26 | public void noGameTime_withOrWithoutIncrement_returnsNull() { 27 | // when 28 | String noParamsName = formatter.prepareAutoName(0, 0, 0, 0); 29 | String incrementOnlyName = formatter.prepareAutoName(0, 0, 1, 1); 30 | 31 | // then 32 | assertNull(noParamsName); 33 | assertNull(incrementOnlyName); 34 | } 35 | 36 | @Test 37 | public void minutesOnly_returnsMinutesString() { 38 | // given 39 | int minutes = 10; 40 | String expectedName = "10 min"; 41 | 42 | // when 43 | String name = formatter.prepareAutoName(minutes, 0, 0, 0); 44 | 45 | // then 46 | assertEquals(name, expectedName); 47 | } 48 | 49 | @Test 50 | public void secondsOnly_returnsSecondsString() { 51 | // given 52 | int seconds = 10; 53 | String expectedName = "10 sec"; 54 | 55 | // when 56 | String name = formatter.prepareAutoName(0, seconds, 0, 0); 57 | 58 | // then 59 | assertEquals(name, expectedName); 60 | } 61 | 62 | @Test 63 | public void minutesGameWithSecondsIncrement_returnsProperFormat() { 64 | // given 65 | int gameTimeMinutes = 5; 66 | int incrementSeconds = 2; 67 | String expectedName = "5 min | 2 sec"; 68 | 69 | // when 70 | String name = formatter.prepareAutoName(gameTimeMinutes, 0, 0, incrementSeconds); 71 | 72 | // then 73 | assertEquals(name, expectedName); 74 | } 75 | 76 | @Test 77 | public void secondsGameWithMinutesIncrement_returnsProperFormat() { 78 | // given 79 | int gameTimeSeconds = 55; 80 | int incrementMinutes = 1; 81 | String expectedName = "55 sec | 1 min"; 82 | 83 | // when 84 | String name = formatter.prepareAutoName(0, gameTimeSeconds, incrementMinutes, 0); 85 | 86 | // then 87 | assertEquals(name, expectedName); 88 | } 89 | 90 | @Test 91 | public void gameParamsOnly_returnsFormatWithoutIncrement() { 92 | // given 93 | int gameTimeMinutes = 2; 94 | int gameTimeSeconds = 30; 95 | String expectedName = "2 min 30 sec"; 96 | 97 | // when 98 | String name = formatter.prepareAutoName(gameTimeMinutes, gameTimeSeconds, 0, 0); 99 | 100 | // then 101 | assertEquals(name, expectedName); 102 | } 103 | 104 | @Test 105 | public void fullGameParamsWithFullIncrement_returnsFullFormat() { 106 | // given 107 | int gameTimeMinutes = 2; 108 | int gameTimeSeconds = 30; 109 | int incrementMinutes = 1; 110 | int incrementSeconds = 30; 111 | String expectedName = "2 min 30 sec | 1 min 30 sec"; 112 | 113 | // when 114 | String name = formatter.prepareAutoName(gameTimeMinutes, gameTimeSeconds, incrementMinutes, incrementSeconds); 115 | 116 | // then 117 | assertEquals(name, expectedName); 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | dependencies { 7 | classpath 'com.android.tools.build:gradle:8.7.3' 8 | } 9 | } 10 | 11 | allprojects { 12 | repositories { 13 | google() 14 | mavenCentral() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | android.useAndroidX=true 2 | android.enableJetifier=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChessCom/android-chessclock/9e5862c9ce2caa28f42ad53009a3cb8b7a93decf/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /metadata/en-US/changelogs/26.txt: -------------------------------------------------------------------------------- 1 | Hey, chess-lovers! The best clock app for your over-the-board games just got even better, with more fun features and a polished new look! 2 | 3 | - Brand new, more usable interface for clock and settings! 4 | - Toggle clock sounds as you play 5 | - Customize your button colors! 6 | - Try something new? Pick from a variety of new time-control presets! 7 | 8 | We hope you love this update. If so, please consider giving it a fresh rating or review. It really helps! Thank you - and enjoy your chess. -------------------------------------------------------------------------------- /metadata/en-US/changelogs/27.txt: -------------------------------------------------------------------------------- 1 | Minor sounds update, bugfixes and maintenance. -------------------------------------------------------------------------------- /metadata/en-US/changelogs/28.txt: -------------------------------------------------------------------------------- 1 | Fix edge to edge overlap issue. -------------------------------------------------------------------------------- /metadata/en-US/full_description.txt: -------------------------------------------------------------------------------- 1 | Replace your chess clock with this free game timer! It's easy to use, yet fully featured to handle any time control. 100% free: no in-app purchases, no ads! 2 | 3 | Choose your time control and you're ready to play. The 2nd player presses her button to start the 1st player's clock - and the game is on! 4 | 5 | FEATURES 6 | - Large, easy-to-read buttons 7 | - Works in landscape and portrait on all devices 8 | - Quickly customize the app for one-tap access to all your favorite time controls 9 | - Time controls include base minutes per player and optional per-move delays or bonus time. The app supports both Fischer and Bronstein increments, as well as simple delays. The duration is up to you! 10 | - Supports multiple-stage time controls commonly seen in tournaments, such as "40 moves in 2 hours + game in 60 minutes." A glance at the clock shows your current stage! 11 | - Clock pauses automatically if the app is interrupted; manually pause the clock at any time 12 | - Pleasant sounds for buttons and "time's up" alert 13 | 14 | At Chess.com, we really do love chess, and sometimes we even play it offline! For times like that, we created this clock - and we hope you love it as much as we do. -------------------------------------------------------------------------------- /metadata/en-US/images/featureGraphic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChessCom/android-chessclock/9e5862c9ce2caa28f42ad53009a3cb8b7a93decf/metadata/en-US/images/featureGraphic.jpg -------------------------------------------------------------------------------- /metadata/en-US/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChessCom/android-chessclock/9e5862c9ce2caa28f42ad53009a3cb8b7a93decf/metadata/en-US/images/icon.png -------------------------------------------------------------------------------- /metadata/en-US/images/phoneScreenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChessCom/android-chessclock/9e5862c9ce2caa28f42ad53009a3cb8b7a93decf/metadata/en-US/images/phoneScreenshots/1.png -------------------------------------------------------------------------------- /metadata/en-US/images/phoneScreenshots/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChessCom/android-chessclock/9e5862c9ce2caa28f42ad53009a3cb8b7a93decf/metadata/en-US/images/phoneScreenshots/2.png -------------------------------------------------------------------------------- /metadata/en-US/images/phoneScreenshots/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChessCom/android-chessclock/9e5862c9ce2caa28f42ad53009a3cb8b7a93decf/metadata/en-US/images/phoneScreenshots/3.png -------------------------------------------------------------------------------- /metadata/en-US/images/phoneScreenshots/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChessCom/android-chessclock/9e5862c9ce2caa28f42ad53009a3cb8b7a93decf/metadata/en-US/images/phoneScreenshots/4.png -------------------------------------------------------------------------------- /metadata/en-US/images/phoneScreenshots/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChessCom/android-chessclock/9e5862c9ce2caa28f42ad53009a3cb8b7a93decf/metadata/en-US/images/phoneScreenshots/5.png -------------------------------------------------------------------------------- /metadata/en-US/images/phoneScreenshots/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChessCom/android-chessclock/9e5862c9ce2caa28f42ad53009a3cb8b7a93decf/metadata/en-US/images/phoneScreenshots/6.png -------------------------------------------------------------------------------- /metadata/en-US/images/sevenInchScreenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChessCom/android-chessclock/9e5862c9ce2caa28f42ad53009a3cb8b7a93decf/metadata/en-US/images/sevenInchScreenshots/1.png -------------------------------------------------------------------------------- /metadata/en-US/images/sevenInchScreenshots/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChessCom/android-chessclock/9e5862c9ce2caa28f42ad53009a3cb8b7a93decf/metadata/en-US/images/sevenInchScreenshots/2.png -------------------------------------------------------------------------------- /metadata/en-US/images/tenInchScreenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChessCom/android-chessclock/9e5862c9ce2caa28f42ad53009a3cb8b7a93decf/metadata/en-US/images/tenInchScreenshots/1.png -------------------------------------------------------------------------------- /metadata/en-US/images/tenInchScreenshots/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChessCom/android-chessclock/9e5862c9ce2caa28f42ad53009a3cb8b7a93decf/metadata/en-US/images/tenInchScreenshots/2.png -------------------------------------------------------------------------------- /metadata/en-US/short_description.txt: -------------------------------------------------------------------------------- 1 | Adjust your favorite timer to play chess online with friends! -------------------------------------------------------------------------------- /metadata/en-US/title.txt: -------------------------------------------------------------------------------- 1 | Chess Clock -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------