Merge pull request #57 from SecUSo/Sudoku-v3.0

Sudoku v3.0
- Added Darktheme
- Added Custom Sudokus
- Added Daily Sudoku
- Added Sudoku Sharing
- Several Bugfixes
Fixes #45, Fixes #24
This commit is contained in:
Christopher Beckmann 2020-10-05 14:22:57 +02:00 committed by GitHub
commit 70a0a80b5f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
188 changed files with 6239 additions and 789 deletions

View file

@ -11,8 +11,8 @@ android {
applicationId "org.secuso.privacyfriendlysudoku" applicationId "org.secuso.privacyfriendlysudoku"
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 29 targetSdkVersion 29
versionCode 8 versionCode 9
versionName "2.2.1" versionName "3.0.0"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
} }
buildTypes { buildTypes {
@ -25,16 +25,23 @@ android {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
} }
dataBinding {
enabled = true
}
} }
dependencies { dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs') implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'androidx.preference:preference:1.1.1'
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
implementation 'androidx.core:core:1.2.0' implementation 'androidx.core:core:1.3.2'
implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.legacy:legacy-support-v13:1.0.0' implementation 'androidx.legacy:legacy-support-v13:1.0.0'
implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.1.0' implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.legacy:legacy-support-core-ui:1.0.0' implementation 'androidx.legacy:legacy-support-core-ui:1.0.0'
implementation 'androidx.legacy:legacy-support-core-utils:1.0.0' implementation 'androidx.legacy:legacy-support-core-utils:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
} }

View file

@ -2,34 +2,39 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.secuso.privacyfriendlysudoku.ui.view"> package="org.secuso.privacyfriendlysudoku.ui.view">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application <application
android:name="org.secuso.privacyfriendlysudoku.SudokuApp"
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme" android:theme="@style/AppTheme">
android:name="org.secuso.privacyfriendlysudoku.SudokuApp">
<activity <activity
android:name="org.secuso.privacyfriendlysudoku.ui.SplashActivity" android:name="org.secuso.privacyfriendlysudoku.ui.SplashActivity"
android:theme="@style/SplashTheme"> android:theme="@style/SplashTheme">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
android:name="org.secuso.privacyfriendlysudoku.ui.MainActivity" android:name="org.secuso.privacyfriendlysudoku.ui.MainActivity"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar"> android:theme="@style/AppTheme.NoActionBar" />
</activity>
<activity <activity
android:name="org.secuso.privacyfriendlysudoku.ui.TutorialActivity" android:name="org.secuso.privacyfriendlysudoku.ui.TutorialActivity"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar"> android:theme="@style/AppTheme.NoActionBar" />
</activity> <activity
android:name="org.secuso.privacyfriendlysudoku.ui.DailySudokuActivity"
android:label="@string/Sudoku"
android:theme="@style/AppTheme.NoActionBar" />
<activity
android:name="org.secuso.privacyfriendlysudoku.ui.CreateSudokuActivity"
android:label="@string/Sudoku"
android:theme="@style/AppTheme.NoActionBar" />
<activity <activity
android:name="org.secuso.privacyfriendlysudoku.ui.SettingsActivity" android:name="org.secuso.privacyfriendlysudoku.ui.SettingsActivity"
android:label="@string/title_activity_settings" android:label="@string/title_activity_settings"
@ -37,7 +42,26 @@
<activity <activity
android:name="org.secuso.privacyfriendlysudoku.ui.GameActivity" android:name="org.secuso.privacyfriendlysudoku.ui.GameActivity"
android:label="@string/title_activity_game_view" android:label="@string/title_activity_game_view"
android:theme="@style/AppTheme.NoActionBar" /> android:launchMode="singleTask"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="sudoku" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="sudoku.secuso.org"
android:scheme="http" />
<data
android:host="sudoku.secuso.org"
android:scheme="https" />
</intent-filter>
</activity>
<activity android:name="org.secuso.privacyfriendlysudoku.ui.AboutActivity" /> <activity android:name="org.secuso.privacyfriendlysudoku.ui.AboutActivity" />
<activity android:name="org.secuso.privacyfriendlysudoku.ui.LoadGameActivity" /> <activity android:name="org.secuso.privacyfriendlysudoku.ui.LoadGameActivity" />
<activity <activity

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku; package org.secuso.privacyfriendlysudoku;
import android.app.Application; import android.app.Application;

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.controller; package org.secuso.privacyfriendlysudoku.controller;
import android.content.Context; import android.content.Context;
@ -6,7 +22,10 @@ import android.os.Handler;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import org.secuso.privacyfriendlysudoku.controller.database.DatabaseHelper;
import org.secuso.privacyfriendlysudoku.controller.database.model.DailySudoku;
import org.secuso.privacyfriendlysudoku.controller.helper.GameInfoContainer; import org.secuso.privacyfriendlysudoku.controller.helper.GameInfoContainer;
import org.secuso.privacyfriendlysudoku.controller.qqwing.QQWing;
import org.secuso.privacyfriendlysudoku.game.CellConflict; import org.secuso.privacyfriendlysudoku.game.CellConflict;
import org.secuso.privacyfriendlysudoku.game.CellConflictList; import org.secuso.privacyfriendlysudoku.game.CellConflictList;
import org.secuso.privacyfriendlysudoku.game.GameBoard; import org.secuso.privacyfriendlysudoku.game.GameBoard;
@ -19,7 +38,10 @@ import org.secuso.privacyfriendlysudoku.game.listener.IHighlightChangedListener;
import org.secuso.privacyfriendlysudoku.game.listener.IHintListener; import org.secuso.privacyfriendlysudoku.game.listener.IHintListener;
import org.secuso.privacyfriendlysudoku.game.listener.IModelChangedListener; import org.secuso.privacyfriendlysudoku.game.listener.IModelChangedListener;
import org.secuso.privacyfriendlysudoku.game.listener.ITimerListener; import org.secuso.privacyfriendlysudoku.game.listener.ITimerListener;
import org.secuso.privacyfriendlysudoku.ui.GameActivity;
import java.util.Calendar;
import java.util.Date;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Timer; import java.util.Timer;
@ -32,6 +54,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
public class GameController implements IModelChangedListener, Parcelable { public class GameController implements IModelChangedListener, Parcelable {
// General // General
public static final int DAILY_SUDOKU_ID = Integer.MAX_VALUE - 1;
private SharedPreferences settings; private SharedPreferences settings;
// View // View
@ -57,6 +80,7 @@ public class GameController implements IModelChangedListener, Parcelable {
private GameType gameType; private GameType gameType;
private GameDifficulty difficulty; private GameDifficulty difficulty;
private CellConflictList errorList = new CellConflictList(); private CellConflictList errorList = new CellConflictList();
private boolean gameIsCustom;
// Undo Redo // Undo Redo
private UndoRedoManager undoRedoManager; private UndoRedoManager undoRedoManager;
@ -84,6 +108,7 @@ public class GameController implements IModelChangedListener, Parcelable {
public GameController(GameType type, SharedPreferences pref, Context context) { public GameController(GameType type, SharedPreferences pref, Context context) {
this.context = context; this.context = context;
this.gameBoard = new GameBoard(type); this.gameBoard = new GameBoard(type);
this.gameIsCustom = false;
setGameType(type); setGameType(type);
setSettings(pref); setSettings(pref);
@ -95,6 +120,8 @@ public class GameController implements IModelChangedListener, Parcelable {
return gameID; return gameID;
} }
public boolean gameIsCustom() { return gameIsCustom; }
public void loadNewLevel(GameType type, GameDifficulty difficulty) { public void loadNewLevel(GameType type, GameDifficulty difficulty) {
NewLevelManager newLevelManager = NewLevelManager.getInstance(context, settings); NewLevelManager newLevelManager = NewLevelManager.getInstance(context, settings);
@ -105,6 +132,23 @@ public class GameController implements IModelChangedListener, Parcelable {
newLevelManager.checkAndRestock(); newLevelManager.checkAndRestock();
} }
public void loadNewDailySudokuLevel() {
NewLevelManager newLevelManager = NewLevelManager.getInstance(context, settings);
// generate the daily sudoku
int[] level = newLevelManager.loadDailySudoku();
// calculate the difficulty of the daily sudoku
QQWing difficultyCheck = new QQWing(GameType.Default_9x9, GameDifficulty.Unspecified);
difficultyCheck.setRecordHistory(true);
difficultyCheck.setPuzzle(level);
difficultyCheck.solve();
loadLevel(new GameInfoContainer(DAILY_SUDOKU_ID, difficultyCheck.getDifficulty(),
GameType.Default_9x9, level, null, null));
}
public int getTime() { public int getTime() {
return time; return time;
} }
@ -117,6 +161,7 @@ public class GameController implements IModelChangedListener, Parcelable {
this.difficulty = gic.getDifficulty(); this.difficulty = gic.getDifficulty();
this.time = gic.getTimePlayed(); this.time = gic.getTimePlayed();
this.usedHints = gic.getHintsUsed(); this.usedHints = gic.getHintsUsed();
this.gameIsCustom = gic.isCustom();
setGameType(gic.getGameType()); setGameType(gic.getGameType());
this.gameBoard = new GameBoard(gic.getGameType()); this.gameBoard = new GameBoard(gic.getGameType());
@ -305,7 +350,7 @@ public class GameController implements IModelChangedListener, Parcelable {
SharedPreferences.Editor editor = settings.edit(); SharedPreferences.Editor editor = settings.edit();
// is anyone ever gonna play so many levels? :) // is anyone ever gonna play so many levels? :)
if(gameID == Integer.MAX_VALUE-1) { if(gameID == DAILY_SUDOKU_ID - 1) {
editor.putInt("lastGameID", 1); editor.putInt("lastGameID", 1);
} else { } else {
editor.putInt("lastGameID", gameID); editor.putInt("lastGameID", gameID);
@ -318,6 +363,31 @@ public class GameController implements IModelChangedListener, Parcelable {
fm.saveGameState(this); fm.saveGameState(this);
} }
/**
* Save progress on the current daily sudoku
* @param context the context in which this method is called
*/
public void saveDailySudoku(Context context) {
int amountOfCells = size * size;
int[] encodedBoard = new int[amountOfCells];
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
encodedBoard[i * size + j] = gameBoard.getCell(i, j).getValue();
}
}
// turn the current date into an id
Calendar currentDate = Calendar.getInstance();
int id = currentDate.get(Calendar.DAY_OF_MONTH) * 1000000
+ (currentDate.get(Calendar.MONTH) + 1) * 10000 + currentDate.get(Calendar.YEAR);
// save the sudoku to the database using the previously calculated id
DatabaseHelper db = new DatabaseHelper(context);
DailySudoku dailySudoku = new DailySudoku(id, difficulty, gameType, encodedBoard, usedHints, GameActivity.timeToString(time));
db.addDailySudoku(dailySudoku);
}
public void deleteGame(Context context) { public void deleteGame(Context context) {
if(gameID == 0) { if(gameID == 0) {
throw new IllegalArgumentException("GameID may not be 0."); throw new IllegalArgumentException("GameID may not be 0.");
@ -414,6 +484,7 @@ public class GameController implements IModelChangedListener, Parcelable {
return gameBoard.toString(); return gameBoard.toString();
} }
public String getCodeOfField() { return gameBoard.transformToCode(); }

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.controller; package org.secuso.privacyfriendlysudoku.controller;
import android.content.Context; import android.content.Context;
@ -20,6 +36,7 @@ public class GameStateManager {
Context context; Context context;
private SharedPreferences settings; private SharedPreferences settings;
private boolean includesDaily;
private static String FILE_EXTENSION = ".txt"; private static String FILE_EXTENSION = ".txt";
private static String SAVE_PREFIX = "save_"; private static String SAVE_PREFIX = "save_";
@ -87,6 +104,15 @@ public class GameStateManager {
gic.parseSetValues(values[i++]); gic.parseSetValues(values[i++]);
gic.parseNotes(values[i++]); gic.parseNotes(values[i++]);
gic.parseHintsUsed(values[i++]); gic.parseHintsUsed(values[i++]);
if (values.length > i) {
gic.setCustom(true);
}
if (gic.getID() == GameController.DAILY_SUDOKU_ID) {
includesDaily = true;
}
} catch(IllegalArgumentException e) { } catch(IllegalArgumentException e) {
file.delete(); file.delete();
continue; continue;
@ -108,7 +134,7 @@ public class GameStateManager {
LinkedList<GameInfoContainer> removeList = new LinkedList<>(); LinkedList<GameInfoContainer> removeList = new LinkedList<>();
for(int i = 0; i < list.size(); i++) { for(int i = 0; i < list.size(); i++) {
if(i >= MAX_NUM_OF_SAVED_GAMES) { if((i >= MAX_NUM_OF_SAVED_GAMES && !includesDaily) || i > MAX_NUM_OF_SAVED_GAMES) {
deleteGameStateFile(list.get(i)); deleteGameStateFile(list.get(i));
removeList.add(list.get(i)); removeList.add(list.get(i));
} }

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.controller; package org.secuso.privacyfriendlysudoku.controller;
import android.app.IntentService; import android.app.IntentService;

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.controller; package org.secuso.privacyfriendlysudoku.controller;
/** /**

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.controller; package org.secuso.privacyfriendlysudoku.controller;
import android.content.Context; import android.content.Context;
@ -15,6 +31,9 @@ import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Random;
@ -37,6 +56,10 @@ public class NewLevelManager {
public static int PRE_SAVES_MIN = 3; public static int PRE_SAVES_MIN = 3;
public static int PRE_SAVES_MAX = 10; public static int PRE_SAVES_MAX = 10;
private final double CHALLENGE_GENERATION_PROBABILITY = 0.25;
private final int CHALLENGE_ITERATIONS = 4;
public static NewLevelManager getInstance(Context context, SharedPreferences settings) { public static NewLevelManager getInstance(Context context, SharedPreferences settings) {
if(instance == null) { if(instance == null) {
instance = new NewLevelManager(context, settings); instance = new NewLevelManager(context, settings);
@ -75,6 +98,16 @@ public class NewLevelManager {
return false; return false;
} }
public int[] loadDailySudoku() {
// create a seed from the current date
DateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");
String toHash = "Sudoku/.PrivacyFriendly/." + dateFormat.format(new Date());
QQWingController controller = new QQWingController();
// generate new sudoku using the previously computed seed
return controller.generateFromSeed(toHash.hashCode(), CHALLENGE_GENERATION_PROBABILITY, CHALLENGE_ITERATIONS);
}
public int[] loadLevel(GameType type, GameDifficulty diff) { public int[] loadLevel(GameType type, GameDifficulty diff) {
Level level = dbHelper.getLevel(diff, type); Level level = dbHelper.getLevel(diff, type);
dbHelper.deleteLevel(level.getId()); dbHelper.deleteLevel(level.getId());

View file

@ -1,9 +1,26 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.controller; package org.secuso.privacyfriendlysudoku.controller;
import android.os.Parcelable; import android.os.Parcelable;
import android.util.Log; import android.util.Log;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Random;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.Date; import java.util.Date;
@ -53,6 +70,49 @@ public class QQWingController {
return generated; return generated;
} }
/**
* Generate a new sudoku based on a given seed regardless of outcome difficulty
* @param seed the seed based on which the sudoku should be calculated
* @return the generated sudoku
*/
public int[] generateFromSeed(int seed) {
return generateFromSeed(seed, 1, 1);
}
/**
* Generate a new sudoku based on a given seed, but only accept challenge sudokus with a certain probability
* @param seed the seed based on which the sudoku should be calculated
* @param challengePermission the probability with which a challenge sudoku is accepted upon calculation
* @param challengeIterations the amount of times a challenge sudoku can be rejected in a row before being
* accepted with a probability of 100%
* @return the generated sudoku
*/
public int[] generateFromSeed(int seed, double challengePermission, int challengeIterations) {
generated.clear();
QQWing generator = new QQWing(GameType.Default_9x9, GameDifficulty.Unspecified);
boolean continueSearch = true;
Random random = new Random(seed);
int seedFactor = 2;
while(continueSearch && challengeIterations > 0) {
seed *= seedFactor;
generator.setRandom(seed);
generator.setRecordHistory(true);
generator.generatePuzzle();
if (generator.getDifficulty() != GameDifficulty.Challenge || random.nextDouble() < challengePermission) {
continueSearch = false;
} else {
challengeIterations--;
}
}
generated.add(generator.getPuzzle());
opts.gameType = GameType.Default_9x9;
opts.gameDifficulty = generator.getDifficulty();
return generated.poll();
}
public int[] solve(GameBoard gameBoard) { public int[] solve(GameBoard gameBoard) {
level = new int[gameBoard.getSize()*gameBoard.getSize()]; level = new int[gameBoard.getSize()*gameBoard.getSize()];
@ -200,6 +260,10 @@ public class QQWingController {
} }
} }
public boolean isImpossible() {
return solveImpossible;
}
private static class QQWingOptions { private static class QQWingOptions {
// defaults for options // defaults for options
boolean needNow = false; boolean needNow = false;

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.controller; package org.secuso.privacyfriendlysudoku.controller;
import android.content.Context; import android.content.Context;
@ -204,12 +220,12 @@ public class SaveLoadStatistics implements ITimerListener, IHintListener {
@Override @Override
public void onTick(int time) { public void onTick(int time) {
incTime(gc.getDifficulty(), gc.getGameType()); if (!gc.gameIsCustom()) incTime(gc.getDifficulty(), gc.getGameType());
//gc.getUsedHints(); //gc.getUsedHints();
} }
@Override @Override
public void onHintUsed() { public void onHintUsed() {
incHints(gc.getDifficulty(),gc.getGameType()); if (!gc.gameIsCustom()) incHints(gc.getDifficulty(),gc.getGameType());
} }
} }

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.controller; package org.secuso.privacyfriendlysudoku.controller;
/** /**

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.controller; package org.secuso.privacyfriendlysudoku.controller;
import android.os.Parcel; import android.os.Parcel;

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.controller.database; package org.secuso.privacyfriendlysudoku.controller.database;
import android.content.ContentValues; import android.content.ContentValues;
@ -6,7 +22,10 @@ import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
import org.secuso.privacyfriendlysudoku.controller.database.columns.DailySudokuColumns;
import org.secuso.privacyfriendlysudoku.controller.database.columns.LevelColumns; import org.secuso.privacyfriendlysudoku.controller.database.columns.LevelColumns;
import org.secuso.privacyfriendlysudoku.controller.database.migration.MigrationUtil;
import org.secuso.privacyfriendlysudoku.controller.database.model.DailySudoku;
import org.secuso.privacyfriendlysudoku.controller.database.model.Level; import org.secuso.privacyfriendlysudoku.controller.database.model.Level;
import org.secuso.privacyfriendlysudoku.game.GameDifficulty; import org.secuso.privacyfriendlysudoku.game.GameDifficulty;
import org.secuso.privacyfriendlysudoku.game.GameType; import org.secuso.privacyfriendlysudoku.game.GameType;
@ -18,7 +37,7 @@ import java.util.List;
public class DatabaseHelper extends SQLiteOpenHelper { public class DatabaseHelper extends SQLiteOpenHelper {
public static final int DATABASE_VERSION = 1; public static final int DATABASE_VERSION = 2;
public static final String DATABASE_NAME = "Database.db"; public static final String DATABASE_NAME = "Database.db";
public DatabaseHelper(Context context) { public DatabaseHelper(Context context) {
@ -28,11 +47,16 @@ public class DatabaseHelper extends SQLiteOpenHelper {
@Override @Override
public void onCreate(SQLiteDatabase db) { public void onCreate(SQLiteDatabase db) {
db.execSQL(LevelColumns.SQL_CREATE_ENTRIES); db.execSQL(LevelColumns.SQL_CREATE_ENTRIES);
db.execSQL(DailySudokuColumns.SQL_CREATE_ENTRIES);
} }
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL(LevelColumns.SQL_DELETE_ENTRIES); // fallback to destructive migration if no migration could be executed
onCreate(db); if(!MigrationUtil.executeMigration(db, oldVersion, newVersion)) {
db.execSQL(LevelColumns.SQL_DELETE_ENTRIES);
db.execSQL(DailySudokuColumns.SQL_DELETE_ENTRIES);
onCreate(db);
}
} }
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
@ -80,6 +104,39 @@ public class DatabaseHelper extends SQLiteOpenHelper {
return levelList.get(0); return levelList.get(0);
} }
/**
* Returns a list of all the daily sudokus that have been solved and thus saved to the database
* @return a list of all the daily sudokus that have been solved so far
*/
public synchronized List<DailySudoku> getDailySudokus() {
List<DailySudoku> dailySudokuList = new LinkedList<>();
SQLiteDatabase database = getWritableDatabase();
// order results from most to least recent
String order = DailySudokuColumns._ID + " DESC";
// How you want the results sorted in the resulting Cursor
Cursor c = database.query(
DailySudokuColumns.TABLE_NAME, // The table to query
DailySudokuColumns.PROJECTION, // The columns to return
null, // select all rows
null, // select all rows
null, // don't group the rows
null, // don't filter by row groups
order // The sort order
);
if (c != null) {
while(c.moveToNext()) {
dailySudokuList.add(DailySudokuColumns.getLevel(c));
}
}
c.close();
return dailySudokuList;
}
public synchronized void deleteLevel(int id) { public synchronized void deleteLevel(int id) {
SQLiteDatabase database = getWritableDatabase(); SQLiteDatabase database = getWritableDatabase();
@ -93,5 +150,15 @@ public class DatabaseHelper extends SQLiteOpenHelper {
SQLiteDatabase database = getWritableDatabase(); SQLiteDatabase database = getWritableDatabase();
return database.insert(LevelColumns.TABLE_NAME, null, LevelColumns.getValues(level)); return database.insert(LevelColumns.TABLE_NAME, null, LevelColumns.getValues(level));
} }
/**
* Adds a new daily sudoku to the database
* @param ds the daily sudoku which is to be added to the database
* @return the row id of the newly inserted sudoku (or -1 if an error occurred)
*/
public synchronized long addDailySudoku(DailySudoku ds) {
SQLiteDatabase database = getWritableDatabase();
return database.insert(DailySudokuColumns.TABLE_NAME, null, DailySudokuColumns.getValues(ds));
}
} }

View file

@ -0,0 +1,89 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.controller.database.columns;
import android.content.ContentValues;
import android.database.Cursor;
import android.provider.BaseColumns;
import org.secuso.privacyfriendlysudoku.controller.database.model.DailySudoku;
import org.secuso.privacyfriendlysudoku.controller.database.model.Level;
/**
* Defines a database schema for saving daily sudokus
*/
public class DailySudokuColumns extends LevelColumns {
public static final String TABLE_NAME = "ds_levels";
public static final String HINTS_USED = "ds_hints_used";
public static final String TIME_NEEDED = "ds_time_needed";
public static final String[] PROJECTION = {
_ID,
DIFFICULTY,
GAMETYPE,
PUZZLE,
HINTS_USED,
TIME_NEEDED
};
private static final String TEXT_TYPE = " TEXT ";
private static final String INTEGER_TYPE = " INTEGER ";
private static final String TIME_TYPE = " TIME (0) ";
private static final String COMMA_SEP = ",";
public static String SQL_CREATE_ENTRIES =
"CREATE TABLE " + TABLE_NAME + " (" +
_ID + INTEGER_TYPE + " PRIMARY KEY" + COMMA_SEP +
DIFFICULTY + TEXT_TYPE + COMMA_SEP +
GAMETYPE + TEXT_TYPE + COMMA_SEP +
PUZZLE + TEXT_TYPE + COMMA_SEP +
HINTS_USED + INTEGER_TYPE + COMMA_SEP +
TIME_NEEDED + TIME_TYPE + " )";
public static String SQL_DELETE_ENTRIES =
"DROP TABLE IF EXISTS " + TABLE_NAME;
/**
* Create a new DailySudoku object using the data stored in a specific database row
* @param c the cursor pointing to the row whose data should be used
* @return the DailySudoku object created using the data from the database row
*/
public static DailySudoku getLevel(Cursor c) {
Level level = LevelColumns.getLevel(c);
int hintsUsed = c.getInt(c.getColumnIndexOrThrow(HINTS_USED));
String timeNeeded = c.getString(c.getColumnIndexOrThrow(TIME_NEEDED));
return new DailySudoku(level.getId(), level.getDifficulty(), level.getGameType(), level.getPuzzle(), hintsUsed, timeNeeded);
}
/**
* Given a specific DailySudoku instance, extracts all relevant parameters and saves them to a ContentValues object
* @param record the DailySudoku instance whose parameters should be extracted
* @return the ContentValues instance containing the extracted parameters
*/
public static ContentValues getValues(DailySudoku record) {
ContentValues result = LevelColumns.getValues(record);
result.put(HINTS_USED, record.getHintsUsed());
result.put(TIME_NEEDED, record.getTimeNeeded());
return result;
}
}

View file

@ -1,3 +1,20 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.controller.database.columns; package org.secuso.privacyfriendlysudoku.controller.database.columns;
import android.content.ContentValues; import android.content.ContentValues;

View file

@ -0,0 +1,35 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.controller.database.migration;
import android.database.sqlite.SQLiteDatabase;
/**
* Similar idea to room migration class.
* @author Christopher Beckmann
*/
public abstract class Migration {
int from = 0;
int to = 0;
public Migration(int from, int to) {
this.from = from;
this.to = to;
}
public abstract void migrate(SQLiteDatabase db);
}

View file

@ -0,0 +1,52 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.controller.database.migration;
import android.database.sqlite.SQLiteDatabase;
import java.util.Arrays;
import java.util.List;
import static org.secuso.privacyfriendlysudoku.controller.database.columns.DailySudokuColumns.SQL_CREATE_ENTRIES;
/**
* @author Christopher Beckmann
*/
public class MigrationUtil {
public static List<Migration> migrations = Arrays.asList(
new Migration(1,2) {
@Override
public void migrate(SQLiteDatabase db) {
db.execSQL(SQL_CREATE_ENTRIES);
}
}
);
//TODO: for now just try to find the desired migration from the list.
// -> When more migrations are added, a chain could be found, e.g. 1->2->3
public static boolean executeMigration(SQLiteDatabase db, int from, int to) {
for(Migration m : migrations) {
if(m.from == from && m.to == to) {
m.migrate(db);
return true;
}
}
return false;
}
}

View file

@ -0,0 +1,86 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.controller.database.model;
import org.secuso.privacyfriendlysudoku.game.GameDifficulty;
import org.secuso.privacyfriendlysudoku.game.GameType;
/**
* Models the content of a single row of the daily sudoku database
*/
public class DailySudoku extends Level {
private int hintsUsed;
private String timeNeeded;
public DailySudoku(int id, GameDifficulty gameDifficulty, GameType gameType, int[] puzzle, int hintsUsed, String timeNeeded) {
super(id, gameDifficulty, gameType, puzzle);
this.hintsUsed = hintsUsed;
this.timeNeeded = timeNeeded;
}
/**
* Return the amount of hints the user needed to solve this sudoku
* @return the amount of hints the user needed to solve this sudoku
*/
public int getHintsUsed() {
return hintsUsed;
}
/**
* Set a new value for the hintsUsed attribute of this daily sudoku
* @param hintsUsed the new value for the hintsUsed attribute
*/
public void setHintsUsed(int hintsUsed) {
this.hintsUsed = hintsUsed;
}
/**
* Return the time the user needed to solve this sudoku as a string
* @return the time the user needed to solve this sudoku as a string
*/
public String getTimeNeeded() {
return timeNeeded;
}
/**
* Return the time the user needed to solve this sudoku in seconds
* @return the time the user needed to solve this sudoku in seconds (or 0 if the timeNeeded parameter
* does not have the right format)
*/
public int getTimeNeededInSeconds() {
if (timeNeeded.matches("[0-9]{2}:[0-5][0-9]:[0-5][0-9]")) {
String[] timeInstances = timeNeeded.split(":");
int hourIndex = 0;
int minuteIndex = 1;
int secondIndex = 2;
int minutes = Integer.parseInt(timeInstances[hourIndex]) * 60 + Integer.parseInt(timeInstances[minuteIndex]);
return minutes * 60 + Integer.parseInt(timeInstances[secondIndex]);
}
return 0;
}
/**
* Set a new value for the timeNeeded attribute of this daily sudoku
* @param timeNeeded the new value for the timeNeeded attribute
*/
public void setTimeNeeded(String timeNeeded) {
this.timeNeeded = timeNeeded;
}
}

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.controller.database.model; package org.secuso.privacyfriendlysudoku.controller.database.model;
import org.secuso.privacyfriendlysudoku.controller.Symbol; import org.secuso.privacyfriendlysudoku.controller.Symbol;

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.controller.helper; package org.secuso.privacyfriendlysudoku.controller.helper;
import android.util.Log; import android.util.Log;
@ -25,6 +41,7 @@ public class GameInfoContainer {
int[] setValues; int[] setValues;
boolean[][] setNotes; boolean[][] setNotes;
int hintsUsed; int hintsUsed;
boolean isCustom;
public GameInfoContainer() {} public GameInfoContainer() {}
public GameInfoContainer(int ID, GameDifficulty difficulty, GameType gameType, int[] fixedValues, int[] setValues, boolean[][] setNotes) { public GameInfoContainer(int ID, GameDifficulty difficulty, GameType gameType, int[] fixedValues, int[] setValues, boolean[][] setNotes) {
@ -40,17 +57,19 @@ public class GameInfoContainer {
this.setValues = setValues; this.setValues = setValues;
this.setNotes = setNotes; this.setNotes = setNotes;
this.hintsUsed = hintsUsed; this.hintsUsed = hintsUsed;
isCustom = false;
} }
public void setID(int ID) { public void setID(int ID) {
this.ID = ID; this.ID = ID;
} }
public void setCustom (boolean isCustom) { this.isCustom = isCustom; }
public boolean isCustom () { return isCustom; }
public void parseGameType(String s) { public void parseGameType(String s) {
gameType = Enum.valueOf(GameType.class, s); gameType = Enum.valueOf(GameType.class, s);
if(gameType == null) {
throw new IllegalArgumentException("GameInfoContainer: gameType could not be set.");
}
} }
public int getTimePlayed() { public int getTimePlayed() {
@ -63,7 +82,7 @@ public class GameInfoContainer {
public void parseTime(String s) { public void parseTime(String s) {
try { try {
this.timePlayed = Integer.valueOf(s); this.timePlayed = Integer.parseInt(s);
} catch(NumberFormatException e) { } catch(NumberFormatException e) {
throw new IllegalArgumentException("GameInfoContainer: Can not parse time.", e); throw new IllegalArgumentException("GameInfoContainer: Can not parse time.", e);
} }
@ -71,7 +90,7 @@ public class GameInfoContainer {
public void parseHintsUsed(String s) { public void parseHintsUsed(String s) {
try { try {
this.hintsUsed = Integer.valueOf(s); this.hintsUsed = Integer.parseInt(s);
} catch(NumberFormatException e) { } catch(NumberFormatException e) {
throw new IllegalArgumentException("GameInfoContainer: Can not parse hints used.", e); throw new IllegalArgumentException("GameInfoContainer: Can not parse hints used.", e);
} }
@ -79,7 +98,7 @@ public class GameInfoContainer {
public void parseDate(String s) { public void parseDate(String s) {
try { try {
this.lastTimePlayed = new Date(Long.valueOf(s)); this.lastTimePlayed = new Date(Long.parseLong(s));
} catch(NumberFormatException e) { } catch(NumberFormatException e) {
throw new IllegalArgumentException("GameInfoContainer: LastTimePlayed Date can not be extracted.", e); throw new IllegalArgumentException("GameInfoContainer: LastTimePlayed Date can not be extracted.", e);
} }
@ -87,9 +106,6 @@ public class GameInfoContainer {
public void parseDifficulty(String s) { public void parseDifficulty(String s) {
difficulty = Enum.valueOf(GameDifficulty.class, s); difficulty = Enum.valueOf(GameDifficulty.class, s);
if(difficulty == null) {
throw new IllegalArgumentException("GameInfoContainer: difficulty could not be set.");
}
} }
public void parseFixedValues(String s){ public void parseFixedValues(String s){
@ -104,6 +120,11 @@ public class GameInfoContainer {
fixedValues = new int[s.length()]; fixedValues = new int[s.length()];
for(int i = 0; i < s.length(); i++) { for(int i = 0; i < s.length(); i++) {
fixedValues[i] = Symbol.getValue(Symbol.SaveFormat, String.valueOf(s.charAt(i)))+1; fixedValues[i] = Symbol.getValue(Symbol.SaveFormat, String.valueOf(s.charAt(i)))+1;
if (gameType != GameType.Unspecified && gameType != null) {
if (fixedValues[i] < 0 || fixedValues[i] > gameType.getSize()) {
throw new IllegalArgumentException("Fixed values must each be smaller than " + gameType.getSize() + ".");
}
}
} }
} }
@ -128,7 +149,7 @@ public class GameInfoContainer {
int size = gameType.getSize(); int size = gameType.getSize();
int sq = size*size; int sq = size*size;
if(gameType != GameType.Unspecified && gameType != null) { if(gameType != GameType.Unspecified) {
if(strings.length != sq) { if(strings.length != sq) {
throw new IllegalArgumentException("The string array must have "+sq+" entries."); throw new IllegalArgumentException("The string array must have "+sq+" entries.");
} }
@ -174,6 +195,7 @@ public class GameInfoContainer {
public static String getGameInfo(GameController controller) { public static String getGameInfo(GameController controller) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
Date today = new Date(); Date today = new Date();
boolean custom = controller.gameIsCustom();
sb.append(controller.getGameType().name()); sb.append(controller.getGameType().name());
sb.append("/"); sb.append("/");
@ -191,6 +213,12 @@ public class GameInfoContainer {
sb.append("/"); sb.append("/");
sb.append(controller.getUsedHints()); sb.append(controller.getUsedHints());
// add additional information to custom sudokus to ensure they can be distinguished from 'regular' sudokus
if (custom) {
sb.append("/");
sb.append(custom);
}
String result = sb.toString(); String result = sb.toString();
Log.d("getGameInfo", result); Log.d("getGameInfo", result);

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.controller.helper; package org.secuso.privacyfriendlysudoku.controller.helper;
import org.secuso.privacyfriendlysudoku.controller.GameController; import org.secuso.privacyfriendlysudoku.controller.GameController;

View file

@ -5,7 +5,7 @@
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or * the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,

View file

@ -5,7 +5,7 @@
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or * the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,

View file

@ -5,7 +5,7 @@
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or * the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,

View file

@ -5,7 +5,7 @@
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or * the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,

View file

@ -7,7 +7,7 @@
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or * the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
@ -199,6 +199,10 @@ public class QQWing {
return reset(); return reset();
} }
public void setRandom(int seed) {
random = new Random(seed);
}
/** /**
* Reset the board to its initial state with only the givens. This method * Reset the board to its initial state with only the givens. This method
* clears any solution, resets statistics, and clears any history messages. * clears any solution, resets statistics, and clears any history messages.

View file

@ -5,7 +5,7 @@
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or * the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.game; package org.secuso.privacyfriendlysudoku.game;
import android.os.Parcel; import android.os.Parcel;

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.game; package org.secuso.privacyfriendlysudoku.game;
import java.util.ArrayList; import java.util.ArrayList;

View file

@ -1,8 +1,25 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.game; package org.secuso.privacyfriendlysudoku.game;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import org.secuso.privacyfriendlysudoku.controller.Symbol;
import org.secuso.privacyfriendlysudoku.game.listener.IModelChangedListener; import org.secuso.privacyfriendlysudoku.game.listener.IModelChangedListener;
import java.util.LinkedList; import java.util.LinkedList;
@ -214,6 +231,20 @@ public class GameBoard implements Cloneable, Parcelable {
return sb.toString(); return sb.toString();
} }
public String transformToCode () {
StringBuilder sb = new StringBuilder();
for(int i = 0; i < field.length; i++) {
for (int j = 0; j < field[0].length; j++) {
if (field[i][j].getValue() == 0) {
sb.append(0);
} else {
sb.append(Symbol.getSymbol(Symbol.SaveFormat, field[i][j].getValue()-1));
}
}
}
return sb.toString();
}
public boolean isFilled() { public boolean isFilled() {
return actionOnCells(new ICellAction<Boolean>() { return actionOnCells(new ICellAction<Boolean>() {
@Override @Override

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.game; package org.secuso.privacyfriendlysudoku.game;
import android.os.Parcel; import android.os.Parcel;

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.game; package org.secuso.privacyfriendlysudoku.game;
import android.os.Parcel; import android.os.Parcel;

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.game; package org.secuso.privacyfriendlysudoku.game;
/** /**

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.game; package org.secuso.privacyfriendlysudoku.game;
/** /**

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.game; package org.secuso.privacyfriendlysudoku.game;
import android.os.Parcel; import android.os.Parcel;

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.game; package org.secuso.privacyfriendlysudoku.game;
/** /**

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.game.listener; package org.secuso.privacyfriendlysudoku.game.listener;
import org.secuso.privacyfriendlysudoku.game.CellConflict; import org.secuso.privacyfriendlysudoku.game.CellConflict;

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.game.listener; package org.secuso.privacyfriendlysudoku.game.listener;
/** /**

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.game.listener; package org.secuso.privacyfriendlysudoku.game.listener;
/** /**

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.game.listener; package org.secuso.privacyfriendlysudoku.game.listener;
/** /**

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.game.listener; package org.secuso.privacyfriendlysudoku.game.listener;
import org.secuso.privacyfriendlysudoku.game.GameCell; import org.secuso.privacyfriendlysudoku.game.GameCell;

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.game.listener; package org.secuso.privacyfriendlysudoku.game.listener;
/** /**

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.ui; package org.secuso.privacyfriendlysudoku.ui;
import android.graphics.Color; import android.graphics.Color;
@ -11,6 +27,10 @@ import android.widget.TextView;
import org.secuso.privacyfriendlysudoku.ui.view.BuildConfig; import org.secuso.privacyfriendlysudoku.ui.view.BuildConfig;
import org.secuso.privacyfriendlysudoku.ui.view.R; import org.secuso.privacyfriendlysudoku.ui.view.R;
/**
* The AboutActivity is extended by the BaseActivity and is responsible for the content of the AboutActivity.
*/
public class AboutActivity extends BaseActivity { public class AboutActivity extends BaseActivity {
@Override @Override

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.ui; package org.secuso.privacyfriendlysudoku.ui;
import android.content.res.Configuration; import android.content.res.Configuration;

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.ui; package org.secuso.privacyfriendlysudoku.ui;
import android.os.Bundle; import android.os.Bundle;

View file

@ -0,0 +1,294 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.ui;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.graphics.Point;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.view.View;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.appcompat.widget.Toolbar;
import org.secuso.privacyfriendlysudoku.controller.GameController;
import org.secuso.privacyfriendlysudoku.controller.Symbol;
import org.secuso.privacyfriendlysudoku.controller.helper.GameInfoContainer;
import org.secuso.privacyfriendlysudoku.controller.qqwing.QQWing;
import org.secuso.privacyfriendlysudoku.game.GameDifficulty;
import org.secuso.privacyfriendlysudoku.game.GameType;
import org.secuso.privacyfriendlysudoku.ui.listener.IFinalizeDialogFragmentListener;
import org.secuso.privacyfriendlysudoku.ui.listener.IImportDialogFragmentListener;
import org.secuso.privacyfriendlysudoku.ui.view.CreateSudokuSpecialButtonLayout;
import org.secuso.privacyfriendlysudoku.ui.view.R;
import org.secuso.privacyfriendlysudoku.ui.view.SudokuFieldLayout;
import org.secuso.privacyfriendlysudoku.ui.view.SudokuKeyboardLayout;
/**
* The CreateSudokuActivity is an activity which extends the BaseActivity and implements the
* IFinalizeDialogFragementListener. It is used to create custom sudokus, which are passed to the
* GameActivity afterwards.
*/
public class CreateSudokuActivity extends BaseActivity implements IFinalizeDialogFragmentListener, IImportDialogFragmentListener {
GameController gameController;
SharedPreferences sharedPref;
SudokuFieldLayout layout;
SudokuKeyboardLayout keyboard;
CreateSudokuSpecialButtonLayout specialButtonLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
if (sharedPref.getBoolean("pref_keep_screen_on", true)) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
if(savedInstanceState == null) {
gameController = new GameController(sharedPref, getApplicationContext());
Bundle extras = getIntent().getExtras();
GameType gameType = GameType.valueOf(extras.getString("gameType", GameType.Default_9x9.name()));
int sectionSize = gameType.getSize();
int boardSize = sectionSize * sectionSize;
GameInfoContainer container = new GameInfoContainer(0, GameDifficulty.Moderate,
gameType, new int[boardSize], new int[boardSize], new boolean[boardSize][sectionSize]);
gameController.loadLevel(container);
} else {
gameController = savedInstanceState.getParcelable("gameController");
if(gameController != null) {
gameController.removeAllListeners();
gameController.setContextAndSettings(getApplicationContext(), sharedPref);
}
}
setUpLayout();
}
private void setUpLayout() {
setContentView(R.layout.activity_create_sudoku);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setTitle(getString(gameController.getGameType().getStringResID()));
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
layout = (SudokuFieldLayout)findViewById(R.id.sudokuLayout);
layout.setSettingsAndGame(sharedPref, gameController);
keyboard = (SudokuKeyboardLayout) findViewById(R.id.sudokuKeyboardLayout);
keyboard.removeAllViews();
keyboard.setGameController(gameController);
Point p = new Point();
getWindowManager().getDefaultDisplay().getSize(p);
int orientation = getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT ?
LinearLayout.HORIZONTAL : LinearLayout.VERTICAL;
keyboard.setKeyBoard(gameController.getSize(), p.x,layout.getHeight()-p.y, orientation);
specialButtonLayout = (CreateSudokuSpecialButtonLayout) findViewById(R.id.createSudokuLayout);
specialButtonLayout.setButtons(p.x, gameController, keyboard, getFragmentManager(), orientation,
CreateSudokuActivity.this, this, this);
gameController.notifyHighlightChangedListeners();
}
@Override
public boolean onSupportNavigateUp() {
onBackPressed();
return true;
}
@Override
public void onResume(){
super.onResume();
View mainContent = findViewById(R.id.main_content);
if (mainContent != null) {
mainContent.animate().alpha(1).setDuration(MAIN_CONTENT_FADEOUT_DURATION);
}
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
Symbol s;
try {
s = Symbol.valueOf(sharedPref.getString("pref_symbols", Symbol.Default.name()));
} catch(IllegalArgumentException e) {
s = Symbol.Default;
}
layout.setSymbols(s);
keyboard.setSymbols(s);
}
@Override
public void onBackPressed() {
finish();
super.onBackPressed();
}
/**
* Verifies an encoded sudoku board by testing whether or not it is uniquely solvable
* @param gameType the type of the encoded sudoku
* @param boardContent the encoded sudoku
* @return whether or not the sudoku is uniquely solvable
*/
public static boolean verify(GameType gameType, String boardContent) {
int boardSize = gameType.getSize() * gameType.getSize();
GameInfoContainer container = new GameInfoContainer(0, GameDifficulty.Unspecified,
gameType, new int [boardSize], new int [boardSize], new boolean [boardSize][gameType.getSize()]);
try {
container.parseFixedValues(boardContent);
} catch (IllegalArgumentException e) {
return false;
}
QQWing verifier = new QQWing(gameType, GameDifficulty.Unspecified);
verifier.setRecordHistory(true);
verifier.setPuzzle(container.getFixedValues());
verifier.solve();
return verifier.hasUniqueSolution();
}
/**
* If the positive button of the FinalizeDialog is clicked, verify the sudoku. Immediately pass
* it to the GameActivity, if the verification process is successful, and do nothing apart from
* notifying the user if not.
* Implements the onFinalizeDialogPositiveClick() method of the IFinalizeDialogFragmentListener
* interface.
*/
public void onFinalizeDialogPositiveClick() {
Toast.makeText(CreateSudokuActivity.this, R.string.verify_custom_sudoku_process_toast, Toast.LENGTH_SHORT).show();
String boardContent = gameController.getCodeOfField();
boolean distinctlySolvable = verify(gameController.getGameType(), boardContent);
if(distinctlySolvable) {
Toast.makeText(CreateSudokuActivity.this, R.string.finished_verifying_custom_sudoku_toast, Toast.LENGTH_LONG).show();
final Intent intent = new Intent(this, GameActivity.class);
/*
Since the GameActivity expects the links of imported sudokus to start with an url scheme,
add one to the start of the encoded board
*/
String scheme = GameActivity.validUris.size() > 0 ? GameActivity.validUris.get(0).getScheme()
+ "://" + GameActivity.validUris.get(0).getHost() : "";
if (!scheme.equals("") && !scheme.endsWith("/")) scheme = scheme + "/";
intent.setData(Uri.parse(scheme + boardContent));
intent.putExtra("isCustom", true);
startActivity(intent);
finish();
} else {
Toast.makeText(CreateSudokuActivity.this, R.string.failed_to_verify_custom_sudoku_toast, Toast.LENGTH_LONG).show();
}
}
public void onImportDialogPositiveClick(String input) {
String inputSudoku = null;
String prefix;
StringBuilder errorMessage = new StringBuilder();
/* remove the present prefix, or, if the input contains none of the valid prefixes, notify the user
that their input is not valid */
for (int i = 0; i < GameActivity.validUris.size(); i++) {
prefix = GameActivity.validUris.get(i).getHost().equals("") ?
GameActivity.validUris.get(i).getScheme() + "://" :
GameActivity.validUris.get(i).getScheme() + "://" + GameActivity.validUris.get(i).getHost() + "/";
if (input.startsWith(prefix)) {
inputSudoku = input.replace(prefix, "");
break;
}
String endOfRecord = (i == GameActivity.validUris.size() - 1) ? "" : ", ";
errorMessage.append(prefix);
errorMessage.append(endOfRecord);
}
// the inputSudoku variable being null means the input did not match any of the valid prefixes
if (inputSudoku == null) {
Toast.makeText(CreateSudokuActivity.this,
this.getString(R.string.menu_import_wrong_format_custom_sudoku) + " " + errorMessage.toString(), Toast.LENGTH_LONG).show();
return;
}
boolean validSize = Math.sqrt(inputSudoku.length()) == gameController.getSize();
if (!validSize) {
Toast.makeText(CreateSudokuActivity.this, R.string.failed_to_verify_custom_sudoku_toast, Toast.LENGTH_LONG).show();
return;
}
//check whether or not the sudoku is valid and has a unique solution
boolean solvable = verify(gameController.getGameType(), inputSudoku);
// if the encoded sudoku is solvable, sent the code directly to the GameActivity; if not, notify the user
if (solvable) {
int boardSize = gameController.getGameType().getSize() * gameController.getGameType().getSize();
GameInfoContainer container = new GameInfoContainer(0, GameDifficulty.Unspecified,
gameController.getGameType(), new int [boardSize], new int [boardSize],
new boolean [boardSize][gameController.getGameType().getSize()]);
container.parseSetValues(inputSudoku);
gameController.loadLevel(container);
setUpLayout();
} else {
Toast.makeText(CreateSudokuActivity.this, R.string.failed_to_verify_custom_sudoku_toast, Toast.LENGTH_LONG).show();
}
}
/**
* Implements the onDialogNegativeClick() method of the IFinalizeDialogFragmentListener
* interface.
*/
public void onDialogNegativeClick() {
}
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
// Always call the superclass so it can save the view hierarchy state
super.onSaveInstanceState(savedInstanceState);
// Save the user's current game state
savedInstanceState.putParcelable("gameController", gameController);
}
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
gameController = savedInstanceState.getParcelable("gameController");
}
}

View file

@ -0,0 +1,272 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.ui;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.view.View;
import android.widget.RatingBar;
import android.widget.Toast;
import org.secuso.privacyfriendlysudoku.controller.GameController;
import org.secuso.privacyfriendlysudoku.controller.GameStateManager;
import org.secuso.privacyfriendlysudoku.controller.NewLevelManager;
import org.secuso.privacyfriendlysudoku.controller.database.DatabaseHelper;
import org.secuso.privacyfriendlysudoku.controller.database.model.DailySudoku;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import org.secuso.privacyfriendlysudoku.controller.qqwing.QQWing;
import org.secuso.privacyfriendlysudoku.game.GameDifficulty;
import org.secuso.privacyfriendlysudoku.game.GameType;
import org.secuso.privacyfriendlysudoku.ui.view.R;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
/**
*The DailySudokuActivity is an activity that extends the AppCompatActivity.
*The activity is responsible for the logic of the DailySudoku.
*DailySudoku is a game mode where every day a different Sudoku is created for the user
*/
public class DailySudokuActivity extends AppCompatActivity {
List<DailySudoku> sudokuList;
SharedPreferences settings;
private final DatabaseHelper dbHelper = new DatabaseHelper(this);
RatingBar difficultyBar;
private SudokuListAdapter sudokuListAdapter;
private int dailyId;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_daily_sudoku);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
sudokuList = dbHelper.getDailySudokus();
TextView totalGamesTextView = findViewById(R.id.numb_of_total_games);
TextView hintsTextView = findViewById(R.id.numb_of_hints);
TextView totalTimeTextView = findViewById(R.id.numb_of_total_time);
totalGamesTextView.setText(String.valueOf(sudokuList.size()));
int sumHints = 0;
int sumTime = 0;
for (DailySudoku sudoku : sudokuList){
sumHints += sudoku.getHintsUsed();
sumTime += sudoku.getTimeNeededInSeconds();
}
int hours = sumTime / 3600;
int minutes = ( sumTime / 60 ) % 60;
int seconds = sumTime % 60;
String str = String.format(Locale.getDefault(), "%02d:%02d:%02d", hours, minutes, seconds);
hintsTextView.setText(String.valueOf(sumHints));
totalTimeTextView.setText(str);
androidx.appcompat.app.ActionBar actionBar = getSupportActionBar();
actionBar.setTitle(R.string.menu_daily_sudoku);
actionBar.setDisplayHomeAsUpEnabled(true);
difficultyBar = findViewById(R.id.first_diff_bar);
settings = PreferenceManager.getDefaultSharedPreferences(this);
ListView listView = (ListView)findViewById(R.id.sudoku_list);
sudokuListAdapter = new SudokuListAdapter(this, sudokuList);
listView.setAdapter(sudokuListAdapter);
// Calculate the current date as an int id
Calendar currentDate = Calendar.getInstance();
dailyId = currentDate.get(Calendar.DAY_OF_MONTH) * 1000000
+ (currentDate.get(Calendar.MONTH) + 1) * 10000 + currentDate.get(Calendar.YEAR);
GameDifficulty dailyDifficulty;
//only calculate the difficulty of the daily sudoku once a day
if (settings.getInt("lastCalculated", 0) != dailyId) {
// generate the daily sudoku
NewLevelManager newLevelManager = NewLevelManager.getInstance(getApplicationContext(), settings);
int[] level = newLevelManager.loadDailySudoku();
QQWing difficultyCheck = new QQWing(GameType.Default_9x9, GameDifficulty.Unspecified);
difficultyCheck.setRecordHistory(true);
difficultyCheck.setPuzzle(level);
difficultyCheck.solve();
dailyDifficulty = difficultyCheck.getDifficulty();
//save the index of the daily difficulty (in the valid difficulty list) and the day it was calculated for
SharedPreferences.Editor editor = settings.edit();
editor.putInt("lastCalculated", dailyId);
editor.putInt("dailyDifficultyIndex", GameDifficulty.getValidDifficultyList().indexOf(dailyDifficulty));
editor.apply();
} else {
// if the daily sudoku has been calculated already, the difficulty can be read from the settings attribute
int index = settings.getInt("dailyDifficultyIndex", GameDifficulty.getValidDifficultyList()
.indexOf(GameDifficulty.Unspecified));
dailyDifficulty = GameDifficulty.getValidDifficultyList().get(index);
}
TextView diffTextView = findViewById(R.id.first_diff_text);
diffTextView.setText(dailyDifficulty.getStringResID());
difficultyBar.setNumStars(GameDifficulty.getValidDifficultyList().size());
difficultyBar.setMax(GameDifficulty.getValidDifficultyList().size());
difficultyBar.setRating(GameDifficulty.getValidDifficultyList().indexOf(dailyDifficulty)+1);
}
public void onClick(View view) {
final Intent intent = new Intent(this,GameActivity.class);
/*
If the 'lastPlayed' key does not return the calculated id, then the player has not played
the sudoku of the day yet, meaning it has yet to be saved on their phone and needs to be generated again
*/
if (settings.getInt("lastPlayed", 0) != dailyId) {
SharedPreferences.Editor editor = settings.edit();
editor.putInt("lastPlayed", dailyId);
//as the player just started a new daily sudoku, set the 'finishedForToday' setting to 'false'
editor.putBoolean("finishedForToday", false);
editor.apply();
//send everything to game activity, which calculates the daily sudoku
intent.putExtra("isDailySudoku", true);
startActivity(intent);
} else if (!settings.getBoolean("finishedForToday", true)) {
/*
if the 'finished for today' setting is 'false', the player has already started the sudoku
but has yet to finish it -> send the designated daily sudoku ID to the GameActivity
*/
GameStateManager fm = new GameStateManager(getBaseContext(), settings);
fm.loadGameStateInfo();
intent.putExtra("loadLevel", true);
intent.putExtra("loadLevelID", GameController.DAILY_SUDOKU_ID);
startActivity(intent);
} else {
Toast.makeText(this, R.string.finished_daily_sudoku, Toast.LENGTH_LONG).show();
}
}
public boolean onCreateOptionsMenu(Menu menu) {
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
//noinspection SimplifiableIfStatement
if (item.getItemId() == android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
private static class SudokuListAdapter extends BaseAdapter {
private Context context;
private List<DailySudoku> sudokuList;
public SudokuListAdapter(Context context, List<DailySudoku> sudokuList) {
this.context = context;
this.sudokuList = sudokuList;
}
@Override
public int getCount() {
return sudokuList.size();
}
@Override
public Object getItem(int position) {
return sudokuList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
DailySudoku sudoku = sudokuList.get(position);
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = (View) inflater.inflate(R.layout.list_entry_layout, null);
}
TextView gameType = (TextView)convertView.findViewById(R.id.loadgame_listentry_gametype);
TextView difficulty =(TextView)convertView.findViewById(R.id.loadgame_listentry_difficultytext);
RatingBar difficultyBar =(RatingBar)convertView.findViewById(R.id.loadgame_listentry_difficultybar);
TextView playedTime = (TextView)convertView.findViewById(R.id.loadgame_listentry_timeplayed);
TextView lastTimePlayed = (TextView)convertView.findViewById(R.id.loadgame_listentry_lasttimeplayed);
ImageView image = (ImageView)convertView.findViewById(R.id.loadgame_listentry_gametypeimage);
ImageView customImage = (ImageView)convertView.findViewById(R.id.loadgame_listentry_custom_icon);
image.setImageResource(R.drawable.icon_default_9x9);
gameType.setText(sudoku.getGameType().getStringResID());
difficulty.setText(sudoku.getDifficulty().getStringResID());
difficultyBar.setNumStars(GameDifficulty.getValidDifficultyList().size());
difficultyBar.setMax(GameDifficulty.getValidDifficultyList().size());
difficultyBar.setRating(GameDifficulty.getValidDifficultyList().indexOf(sudoku.getDifficulty())+1);
customImage.setVisibility(View.INVISIBLE);
int id = sudoku.getId();
Calendar cal = Calendar.getInstance();
cal.set(id%10000, (id/10000) % 100, id/1000000, 0, 0, 0 );
DateFormat format = DateFormat.getDateInstance();
format.setTimeZone(TimeZone.getDefault());
lastTimePlayed.setText(format.format(cal.getTime()));
playedTime.setText(sudoku.getTimeNeeded());
return convertView;
}
}
}

View file

@ -1,24 +1,49 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.ui; package org.secuso.privacyfriendlysudoku.ui;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.app.DialogFragment; import android.app.DialogFragment;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.graphics.Point; import android.graphics.Point;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceActivity; import android.preference.PreferenceActivity;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import com.google.android.material.navigation.NavigationView; import com.google.android.material.navigation.NavigationView;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.view.GravityCompat; import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout; import androidx.drawerlayout.widget.DrawerLayout;
import androidx.appcompat.app.ActionBarDrawerToggle; import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import android.util.Log;
import android.view.Gravity; import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
@ -27,28 +52,40 @@ import android.widget.Button;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.RatingBar; import android.widget.RatingBar;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import org.secuso.privacyfriendlysudoku.controller.GameController; import org.secuso.privacyfriendlysudoku.controller.GameController;
import org.secuso.privacyfriendlysudoku.controller.GameStateManager; import org.secuso.privacyfriendlysudoku.controller.GameStateManager;
import org.secuso.privacyfriendlysudoku.controller.SaveLoadStatistics; import org.secuso.privacyfriendlysudoku.controller.SaveLoadStatistics;
import org.secuso.privacyfriendlysudoku.controller.Symbol; import org.secuso.privacyfriendlysudoku.controller.Symbol;
import org.secuso.privacyfriendlysudoku.controller.helper.GameInfoContainer; import org.secuso.privacyfriendlysudoku.controller.helper.GameInfoContainer;
import org.secuso.privacyfriendlysudoku.controller.qqwing.QQWing;
import org.secuso.privacyfriendlysudoku.game.GameDifficulty; import org.secuso.privacyfriendlysudoku.game.GameDifficulty;
import org.secuso.privacyfriendlysudoku.game.GameType; import org.secuso.privacyfriendlysudoku.game.GameType;
import org.secuso.privacyfriendlysudoku.game.listener.IGameSolvedListener; import org.secuso.privacyfriendlysudoku.game.listener.IGameSolvedListener;
import org.secuso.privacyfriendlysudoku.game.listener.ITimerListener; import org.secuso.privacyfriendlysudoku.game.listener.ITimerListener;
import org.secuso.privacyfriendlysudoku.ui.listener.IHintDialogFragmentListener; import org.secuso.privacyfriendlysudoku.ui.listener.IHintDialogFragmentListener;
import org.secuso.privacyfriendlysudoku.ui.listener.IResetDialogFragmentListener; import org.secuso.privacyfriendlysudoku.ui.listener.IResetDialogFragmentListener;
import org.secuso.privacyfriendlysudoku.ui.listener.IShareDialogFragmentListener;
import org.secuso.privacyfriendlysudoku.ui.view.R; import org.secuso.privacyfriendlysudoku.ui.view.R;
import org.secuso.privacyfriendlysudoku.ui.view.SudokuFieldLayout; import org.secuso.privacyfriendlysudoku.ui.view.SudokuFieldLayout;
import org.secuso.privacyfriendlysudoku.ui.view.SudokuKeyboardLayout; import org.secuso.privacyfriendlysudoku.ui.view.SudokuKeyboardLayout;
import org.secuso.privacyfriendlysudoku.ui.view.SudokuSpecialButtonLayout; import org.secuso.privacyfriendlysudoku.ui.view.SudokuSpecialButtonLayout;
import org.secuso.privacyfriendlysudoku.ui.view.WinDialog; import org.secuso.privacyfriendlysudoku.ui.view.WinDialog;
import org.secuso.privacyfriendlysudoku.ui.view.databinding.DialogFragmentShareBoardBinding;
import java.util.Arrays;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
public class GameActivity extends BaseActivity implements NavigationView.OnNavigationItemSelectedListener, IGameSolvedListener ,ITimerListener, IHintDialogFragmentListener, IResetDialogFragmentListener { public class GameActivity extends BaseActivity implements NavigationView.OnNavigationItemSelectedListener, IGameSolvedListener ,ITimerListener, IHintDialogFragmentListener, IResetDialogFragmentListener, IShareDialogFragmentListener {
public static final List<Uri> validUris = Arrays.asList(
Uri.parse("https://sudoku.secuso.org"),
Uri.parse("http://sudoku.secuso.org"),
Uri.parse("sudoku://")
);
GameController gameController; GameController gameController;
SudokuFieldLayout layout; SudokuFieldLayout layout;
@ -57,15 +94,27 @@ public class GameActivity extends BaseActivity implements NavigationView.OnNavig
TextView timerView; TextView timerView;
TextView viewName ; TextView viewName ;
RatingBar ratingBar; RatingBar ratingBar;
private boolean gameSolved = false;
SaveLoadStatistics statistics = new SaveLoadStatistics(this); SaveLoadStatistics statistics = new SaveLoadStatistics(this);
WinDialog dialog = null; WinDialog dialog = null;
private boolean gameSolved = false;
private boolean startGame = true;
public static String timeToString(int time) {
int seconds = time % 60;
int minutes = ((time - seconds) / 60) % 60;
int hours = (time - minutes - seconds) / (3600);
String h, m, s;
s = (seconds < 10) ? "0" + String.valueOf(seconds) : String.valueOf(seconds);
m = (minutes < 10) ? "0" + String.valueOf(minutes) : String.valueOf(minutes);
h = (hours < 10) ? "0" + String.valueOf(hours) : String.valueOf(hours);
return h + ":" + m + ":" + s;
}
@Override @Override
protected void onPostCreate(Bundle savedInstanceState) { protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState); super.onPostCreate(savedInstanceState);
if(gameSolved) { if(gameSolved || !startGame) {
gameController.pauseTimer(); gameController.pauseTimer();
} else { } else {
// start the game // start the game
@ -80,41 +129,165 @@ public class GameActivity extends BaseActivity implements NavigationView.OnNavig
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
/*
If the app is started via a deeplink, the GameActivity is the first activity the user accesses,
so we need to set the dark mode settings in this activity as well
*/
if (sharedPref.getBoolean("pref_dark_mode_setting", false )) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
} else if (sharedPref.getBoolean("pref_dark_mode_automatically_by_system", false)) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
} else if(sharedPref.getBoolean("pref_dark_mode_automatically_by_battery", false)){
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY);
} else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
}
if(sharedPref.getBoolean("pref_keep_screen_on", true)) { if(sharedPref.getBoolean("pref_keep_screen_on", true)) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
} }
super.onCreate(savedInstanceState);
GameType gameType = GameType.Unspecified; GameType gameType = GameType.Unspecified;
GameDifficulty gameDifficulty = GameDifficulty.Unspecified; GameDifficulty gameDifficulty = GameDifficulty.Unspecified;
int loadLevelID = 0; int loadLevelID = 0;
boolean loadLevel = false; boolean loadLevel = false;
if(savedInstanceState == null) { if(savedInstanceState == null) {
Bundle extras = getIntent().getExtras(); Bundle extras = getIntent().getExtras();
if (extras != null) {
gameType = GameType.valueOf(extras.getString("gameType", GameType.Default_9x9.name()));
gameDifficulty = GameDifficulty.valueOf(extras.getString("gameDifficulty", GameDifficulty.Moderate.name()));
loadLevel = extras.getBoolean("loadLevel", false);
if (loadLevel) {
loadLevelID = extras.getInt("loadLevelID");
}
}
/*
If a (deep) link is used to access the activity, the content of the link cannot be accessed
as a part of the getExtras() bundle. Instead, it needs to be saved as an URI object
*/
Uri data = getIntent().getData();
gameController = new GameController(sharedPref, getApplicationContext()); gameController = new GameController(sharedPref, getApplicationContext());
List<GameInfoContainer> loadableGames = GameStateManager.getLoadableGameList(); // Intents coming from the LoadGameActivity and MainActivity can be identified based on the keys the getExtras() bundle contains
boolean intentReceivedFromMainActivity = extras != null &&
(extras.containsKey("gameType") || extras.containsKey("loadLevel"));
/*
If data is not null and the intent was not received from the MainActivity/ LoadGameActivity, the source of the intent must be the
CreateSudokuActivity, the ImportBoardDialog or a deep link, meaning data carries an URI containing an encoded sudoku
*/
if (data != null && !intentReceivedFromMainActivity) {
// extract encoded sudoku board from the URI
String input = "";
for (int i = 0; i < validUris.size(); i++) {
if (data.getScheme().equals(validUris.get(i).getScheme())) {
if (validUris.get(i).getHost().equals("")) {
input = data.getHost();
break;
}
else if (data.getHost().equals(validUris.get(i).getHost())) {
input = data.getPath();
input =input.replace("/", "");
break;
}
}
}
// Save all of the information that can be extracted from the encoded board in a GameInfoContainer object
int sectionSize = (int)Math.sqrt(input.length());
int boardSize = sectionSize * sectionSize;
QQWing difficultyCheck;
GameInfoContainer container = new GameInfoContainer(0, GameDifficulty.Unspecified,
GameType.Unspecified, new int [boardSize], new int [boardSize], new boolean [boardSize][sectionSize]);
container.setCustom(extras != null && extras.getBoolean("isCustom", false));
try {
container.parseGameType("Default_" + sectionSize + "x" + sectionSize);
container.parseFixedValues(input);
difficultyCheck = new QQWing(container.getGameType(), GameDifficulty.Unspecified);
// calculate difficulty of the imported sudoku
difficultyCheck.setRecordHistory(true);
difficultyCheck.setPuzzle(container.getFixedValues());
difficultyCheck.solve();
container.parseDifficulty(difficultyCheck.getDifficulty().toString());
// A sudoku is that does not have a unique solution is deemed 'unplayable' and may not be started
startGame = difficultyCheck.hasUniqueSolution();
} catch (IllegalArgumentException e) {
// If the imported code does not actually encode a valid sudoku, it needs to be rejected
startGame = false;
/*
set up a blank sudoku field that can be displayed in the activity while the player is notified that
the link they imported does not encode a valid sudoku
*/
sectionSize = GameType.Default_9x9.getSize();
boardSize = sectionSize * sectionSize;
container = new GameInfoContainer(0, GameDifficulty.Unspecified,
GameType.Default_9x9, new int [boardSize], new int [boardSize], new boolean [boardSize][sectionSize]);
}
// Notify the user if the sudoku they tried to import cannot be played and finish the activity
if (!startGame) {
AlertDialog.Builder builder = new AlertDialog.Builder(GameActivity.this, R.style.AppTheme_Dialog);
builder.setMessage(R.string.impossible_import_notice)
.setCancelable(false)
.setPositiveButton(R.string.okay, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
finish();
}
});
AlertDialog alert = builder.create();
alert.show();
}
gameController.loadLevel(container);
if (loadLevel && loadableGames.size() > loadLevelID) {
// load level from GameStateManager
gameController.loadLevel(loadableGames.get(loadLevelID));
} else { } else {
// load a new level boolean isDailySudoku = false;
gameController.loadNewLevel(gameType, gameDifficulty); if (extras != null) {
gameType = GameType.valueOf(extras.getString("gameType", GameType.Default_9x9.name()));
gameDifficulty = GameDifficulty.valueOf(extras.getString("gameDifficulty", GameDifficulty.Moderate.name()));
isDailySudoku = extras.getBoolean("isDailySudoku", false);
loadLevel = extras.getBoolean("loadLevel", false);
if (loadLevel) {
loadLevelID = extras.getInt("loadLevelID");
}
}
/*
The 'isDailySudoku' key is only set by the DailySudokuActivity if a new daily sudoku needs to be calculated;
otherwise, the extras simply contain the id of the daily sudoku. Therefore, calculate the new daily sudoko if
'isDailySudoku' is true
*/
if (isDailySudoku) {
gameController.loadNewDailySudokuLevel();
} else {
List<GameInfoContainer> loadableGames = GameStateManager.getLoadableGameList();
if (loadLevel) {
if (loadableGames.size() > loadLevelID) {
// load level from GameStateManager
gameController.loadLevel(loadableGames.get(loadLevelID));
} else if (loadLevelID == GameController.DAILY_SUDOKU_ID) {
for (GameInfoContainer container : loadableGames) {
if (container.getID() == loadLevelID) {
gameController.loadLevel(container);
break;
}
}
}
} else {
// load a new level
gameController.loadNewLevel(gameType, gameDifficulty);
}
}
} }
} else { } else {
gameController = savedInstanceState.getParcelable("gameController"); gameController = savedInstanceState.getParcelable("gameController");
@ -170,7 +343,7 @@ public class GameActivity extends BaseActivity implements NavigationView.OnNavig
//set Special keys //set Special keys
specialButtonLayout = (SudokuSpecialButtonLayout) findViewById(R.id.sudokuSpecialLayout); specialButtonLayout = (SudokuSpecialButtonLayout) findViewById(R.id.sudokuSpecialLayout);
specialButtonLayout.setButtons(p.x, gameController, keyboard, getFragmentManager(), orientation); specialButtonLayout.setButtons(p.x, gameController, keyboard, getFragmentManager(), orientation, GameActivity.this);
//set TimerView //set TimerView
timerView = (TextView)findViewById(R.id.timerView); timerView = (TextView)findViewById(R.id.timerView);
@ -217,11 +390,21 @@ public class GameActivity extends BaseActivity implements NavigationView.OnNavig
@Override @Override
public void onPause(){ public void onPause(){
super.onPause(); super.onPause();
if(!gameSolved) {
// Do not save solved or unplayable sudokus
if(!gameSolved && startGame) {
gameController.saveGame(this); gameController.saveGame(this);
} }
gameController.deleteTimer(); gameController.deleteTimer();
} }
@Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
startActivity(intent);
finish();
}
@Override @Override
public void onResume(){ public void onResume(){
super.onResume(); super.onResume();
@ -233,7 +416,7 @@ public class GameActivity extends BaseActivity implements NavigationView.OnNavig
gameController.initTimer(); gameController.initTimer();
if(!gameSolved) { if(!gameSolved && startGame) {
mHandler.postDelayed(new Runnable() { mHandler.postDelayed(new Runnable() {
@Override @Override
public void run() { public void run() {
@ -253,6 +436,12 @@ public class GameActivity extends BaseActivity implements NavigationView.OnNavig
keyboard.setSymbols(s); keyboard.setSymbols(s);
} }
/*@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.game_view, menu);
return true;
}*/
@Override @Override
public void onBackPressed() { public void onBackPressed() {
@ -265,13 +454,6 @@ public class GameActivity extends BaseActivity implements NavigationView.OnNavig
} }
} }
/*@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.game_view, menu);
return true;
}*/
@SuppressWarnings("StatementWithEmptyBody") @SuppressWarnings("StatementWithEmptyBody")
@Override @Override
public boolean onNavigationItemSelected(MenuItem item) { public boolean onNavigationItemSelected(MenuItem item) {
@ -286,6 +468,39 @@ public class GameActivity extends BaseActivity implements NavigationView.OnNavig
resetDialog.show(getFragmentManager(), "ResetDialogFragment"); resetDialog.show(getFragmentManager(), "ResetDialogFragment");
break; break;
case R.id.menu_share:
// Create import link from current sudoku board
String scheme = GameActivity.validUris.size() > 0 ? GameActivity.validUris.get(0).getScheme()
+ "://" + GameActivity.validUris.get(0).getHost() : "";
if (!scheme.equals("") && !scheme.endsWith("/")) scheme = scheme + "/";
String codeForClipboard = scheme + gameController.getCodeOfField();
// Create new ShareBoardDialog using the previously created links
ShareBoardDialog shareDialog = new ShareBoardDialog();
shareDialog.setDisplayCode(codeForClipboard);
shareDialog.setCopyClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// remember to include alternate code for older android versions
//save link to clipboard
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
if (clipboard != null) {
ClipData clip = ClipData.newPlainText("BoardCode", codeForClipboard);
clipboard.setPrimaryClip(clip);
Toast.makeText(GameActivity.this, R.string.copy_code_confirmation_toast,
Toast.LENGTH_LONG).show();
} else {
Toast.makeText(GameActivity.this, R.string.copy_code_error_toast,
Toast.LENGTH_LONG).show();
}
}
});
shareDialog.show(getFragmentManager(), "ShareDialogFragment");
break;
case R.id.nav_newgame: case R.id.nav_newgame:
//create new game //create new game
intent = new Intent(this, MainActivity.class); intent = new Intent(this, MainActivity.class);
@ -296,8 +511,6 @@ public class GameActivity extends BaseActivity implements NavigationView.OnNavig
case R.id.menu_settings: case R.id.menu_settings:
//open settings //open settings
intent = new Intent(this,SettingsActivity.class); intent = new Intent(this,SettingsActivity.class);
intent.putExtra( PreferenceActivity.EXTRA_SHOW_FRAGMENT, SettingsActivity.GamePreferenceFragment.class.getName() );
intent.putExtra( PreferenceActivity.EXTRA_NO_HEADERS, true );
break; break;
case R.id.nav_highscore: case R.id.nav_highscore:
@ -341,7 +554,6 @@ public class GameActivity extends BaseActivity implements NavigationView.OnNavig
return true; return true;
} }
@Override @Override
public void onSolved() { public void onSolved() {
gameSolved = true; gameSolved = true;
@ -350,12 +562,33 @@ public class GameActivity extends BaseActivity implements NavigationView.OnNavig
gameController.deleteGame(this); gameController.deleteGame(this);
disableReset(); disableReset();
//Show time hints new plus old best time //Save solved sudoku, if it happens to be a daily sudoku, to daily sudoku database
if(gameController.getGameID() == GameController.DAILY_SUDOKU_ID) {
gameController.saveDailySudoku(GameActivity.this);
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
SharedPreferences.Editor editor = sharedPref.edit();
/*
set 'finishedForToday' setting to 'true', signifying that the player has solved the daily
sudoku and can no longer play it today
*/
editor.putBoolean("finishedForToday", true);
editor.apply();
}
statistics.saveGameStats(); //Don't save statistics if game is custom
boolean isNewBestTime;
if (!gameController.gameIsCustom()) {
//Show time hints new plus old best time
statistics.saveGameStats();
isNewBestTime = gameController.getUsedHints() == 0
&& statistics.loadStats(gameController.getGameType(),gameController.getDifficulty()).getMinTime() >= gameController.getTime();
} else {
// cannot be best time if sudoku is custom
isNewBestTime = false;
}
boolean isNewBestTime = gameController.getUsedHints() == 0
&& statistics.loadStats(gameController.getGameType(),gameController.getDifficulty()).getMinTime() >= gameController.getTime();
dialog = new WinDialog(this, R.style.WinDialog , timeToString(gameController.getTime()), String.valueOf(gameController.getUsedHints()), isNewBestTime); dialog = new WinDialog(this, R.style.WinDialog , timeToString(gameController.getTime()), String.valueOf(gameController.getUsedHints()), isNewBestTime);
@ -395,18 +628,6 @@ public class GameActivity extends BaseActivity implements NavigationView.OnNavig
specialButtonLayout.setButtonsEnabled(false); specialButtonLayout.setButtonsEnabled(false);
} }
public String timeToString(int time) {
int seconds = time % 60;
int minutes = ((time - seconds) / 60) % 60;
int hours = (time - minutes - seconds) / (3600);
String h, m, s;
s = (seconds < 10) ? "0" + String.valueOf(seconds) : String.valueOf(seconds);
m = (minutes < 10) ? "0" + String.valueOf(minutes) : String.valueOf(minutes);
h = (hours < 10) ? "0" + String.valueOf(hours) : String.valueOf(hours);
return h + ":" + m + ":" + s;
}
private void disableReset(){ private void disableReset(){
NavigationView navView = (NavigationView)findViewById(R.id.nav_view); NavigationView navView = (NavigationView)findViewById(R.id.nav_view);
Menu navMenu = navView.getMenu(); Menu navMenu = navView.getMenu();
@ -418,7 +639,7 @@ public class GameActivity extends BaseActivity implements NavigationView.OnNavig
// display the time // display the time
timerView.setText(timeToString(time)); timerView.setText(timeToString(time));
if(gameSolved) return; if(gameSolved || !startGame) return;
// save time // save time
gameController.saveGame(this); gameController.saveGame(this);
} }
@ -433,11 +654,100 @@ public class GameActivity extends BaseActivity implements NavigationView.OnNavig
gameController.resetLevel(); gameController.resetLevel();
} }
@Override
public void onShareDialogPositiveClick(String input) {
Intent sendBoardIntent = new Intent();
sendBoardIntent.setAction(Intent.ACTION_SEND);
sendBoardIntent.putExtra(Intent.EXTRA_TEXT, input);
sendBoardIntent.setType("text/plain");
Intent shareBoardIntent = Intent.createChooser(sendBoardIntent, null);
startActivity(shareBoardIntent);
}
@Override @Override
public void onDialogNegativeClick() { public void onDialogNegativeClick() {
// do nothing // do nothing
} }
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
// Always call the superclass so it can save the view hierarchy state
super.onSaveInstanceState(savedInstanceState);
// Save the user's current game state
savedInstanceState.putParcelable("gameController", gameController);
savedInstanceState.putBoolean("gameSolved", gameSolved);
}
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
gameController = savedInstanceState.getParcelable("gameController");
gameSolved = savedInstanceState.getBoolean("gameSolved");
}
public static class ShareBoardDialog extends DialogFragment {
private LinkedList<IShareDialogFragmentListener> listeners = new LinkedList<>();
/*declare empty display code and click listener in case anyone
* tries to call the ShareBoardDialog without setting those attributes first
*/
private String displayCode = "";
private View.OnClickListener copyClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
}
};
public void setDisplayCode(String displayCode) {
this.displayCode = displayCode;
}
public void setCopyClickListener(View.OnClickListener listener) {
copyClickListener = listener;
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// Verify that the host activity implements the callback interface
if(activity instanceof IShareDialogFragmentListener) {
listeners.add((IShareDialogFragmentListener) activity);
}
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style.AppTheme_Dialog);
LayoutInflater inflater = getActivity().getLayoutInflater();
DialogFragmentShareBoardBinding binding = DialogFragmentShareBoardBinding.inflate(inflater);
binding.ver3DisplaySudokuTextView.setText(displayCode);
binding.ver3DisplaySudokuTextView.setEnabled(false);
binding.ver3CopySudokuToClipboardButton.setOnClickListener(copyClickListener);
builder.setView(binding.getRoot());
builder.setPositiveButton(R.string.share_confirmation_confirm, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
for(IShareDialogFragmentListener l : listeners) {
l.onShareDialogPositiveClick(binding.ver3DisplaySudokuTextView.getText().toString());
}
}
})
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// User cancelled the dialog
}
});
return builder.create();
}
}
public static class ResetConfirmationDialog extends DialogFragment { public static class ResetConfirmationDialog extends DialogFragment {
LinkedList<IResetDialogFragmentListener> listeners = new LinkedList<>(); LinkedList<IResetDialogFragmentListener> listeners = new LinkedList<>();
@ -454,7 +764,7 @@ public class GameActivity extends BaseActivity implements NavigationView.OnNavig
@Override @Override
public Dialog onCreateDialog(Bundle savedInstanceState) { public Dialog onCreateDialog(Bundle savedInstanceState) {
// Use the Builder class for convenient dialog construction // Use the Builder class for convenient dialog construction
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style.AppTheme_Dialog);
builder.setMessage(R.string.reset_confirmation) builder.setMessage(R.string.reset_confirmation)
.setPositiveButton(R.string.reset_confirmation_confirm, new DialogInterface.OnClickListener() { .setPositiveButton(R.string.reset_confirmation_confirm, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) { public void onClick(DialogInterface dialog, int id) {
@ -472,20 +782,4 @@ public class GameActivity extends BaseActivity implements NavigationView.OnNavig
} }
} }
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
// Save the user's current game state
savedInstanceState.putParcelable("gameController", gameController);
savedInstanceState.putInt("gameSolved", gameSolved ? 1 : 0);
// Always call the superclass so it can save the view hierarchy state
super.onSaveInstanceState(savedInstanceState);
}
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
//super.onRestoreInstanceState(savedInstanceState);
}
} }

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.ui; package org.secuso.privacyfriendlysudoku.ui;

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.ui; package org.secuso.privacyfriendlysudoku.ui;
import android.app.Activity; import android.app.Activity;
@ -21,6 +37,7 @@ import android.widget.ListView;
import android.widget.RatingBar; import android.widget.RatingBar;
import android.widget.TextView; import android.widget.TextView;
import org.secuso.privacyfriendlysudoku.controller.GameController;
import org.secuso.privacyfriendlysudoku.controller.GameStateManager; import org.secuso.privacyfriendlysudoku.controller.GameStateManager;
import org.secuso.privacyfriendlysudoku.controller.helper.GameInfoContainer; import org.secuso.privacyfriendlysudoku.controller.helper.GameInfoContainer;
import org.secuso.privacyfriendlysudoku.game.GameDifficulty; import org.secuso.privacyfriendlysudoku.game.GameDifficulty;
@ -70,6 +87,13 @@ public class LoadGameActivity extends BaseActivity implements IDeleteDialogFragm
GameStateManager gameStateManager = new GameStateManager(this, settings); GameStateManager gameStateManager = new GameStateManager(this, settings);
loadableGameList = gameStateManager.loadGameStateInfo(); loadableGameList = gameStateManager.loadGameStateInfo();
for (GameInfoContainer container : loadableGameList) {
if (container.getID() == GameController.DAILY_SUDOKU_ID) {
loadableGameList.remove(container);
break;
}
}
AdapterView.OnItemClickListener clickListener = new AdapterView.OnItemClickListener() { AdapterView.OnItemClickListener clickListener = new AdapterView.OnItemClickListener() {
@Override @Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) { public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
@ -137,7 +161,7 @@ public class LoadGameActivity extends BaseActivity implements IDeleteDialogFragm
@Override @Override
public Dialog onCreateDialog(Bundle savedInstanceState) { public Dialog onCreateDialog(Bundle savedInstanceState) {
// Use the Builder class for convenient dialog construction // Use the Builder class for convenient dialog construction
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style.AppTheme_Dialog);
builder.setMessage(R.string.loadgame_delete_confirmation) builder.setMessage(R.string.loadgame_delete_confirmation)
.setPositiveButton(R.string.loadgame_delete_confirm, new DialogInterface.OnClickListener() { .setPositiveButton(R.string.loadgame_delete_confirm, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) { public void onClick(DialogInterface dialog, int id) {
@ -201,6 +225,8 @@ public class LoadGameActivity extends BaseActivity implements IDeleteDialogFragm
TextView playedTime = (TextView)convertView.findViewById(R.id.loadgame_listentry_timeplayed); TextView playedTime = (TextView)convertView.findViewById(R.id.loadgame_listentry_timeplayed);
TextView lastTimePlayed = (TextView)convertView.findViewById(R.id.loadgame_listentry_lasttimeplayed); TextView lastTimePlayed = (TextView)convertView.findViewById(R.id.loadgame_listentry_lasttimeplayed);
ImageView image = (ImageView)convertView.findViewById(R.id.loadgame_listentry_gametypeimage); ImageView image = (ImageView)convertView.findViewById(R.id.loadgame_listentry_gametypeimage);
ImageView customImage = (ImageView)convertView.findViewById(R.id.loadgame_listentry_custom_icon);
TextView customLabel = (TextView) convertView.findViewById(R.id.loadgame_listentry_custom_label);
switch(gic.getGameType()) { switch(gic.getGameType()) {
case Default_6x6: case Default_6x6:
@ -221,6 +247,9 @@ public class LoadGameActivity extends BaseActivity implements IDeleteDialogFragm
difficultyBar.setMax(GameDifficulty.getValidDifficultyList().size()); difficultyBar.setMax(GameDifficulty.getValidDifficultyList().size());
difficultyBar.setRating(GameDifficulty.getValidDifficultyList().indexOf(gic.getDifficulty())+1); difficultyBar.setRating(GameDifficulty.getValidDifficultyList().indexOf(gic.getDifficulty())+1);
customImage.setImageResource(gic.isCustom() ? R.drawable.ic_circle_blue_36dp : R.drawable.ic_circle_grey_36dp);
customLabel.setVisibility(gic.isCustom() ? View.VISIBLE : View.GONE);
int time = gic.getTimePlayed(); int time = gic.getTimePlayed();
int seconds = time % 60; int seconds = time % 60;
int minutes = ((time -seconds)/60)%60 ; int minutes = ((time -seconds)/60)%60 ;

View file

@ -1,11 +1,34 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.ui; package org.secuso.privacyfriendlysudoku.ui;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import com.google.android.material.navigation.NavigationView; import com.google.android.material.navigation.NavigationView;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter; import androidx.fragment.app.FragmentPagerAdapter;
@ -13,31 +36,36 @@ import androidx.core.view.GravityCompat;
import androidx.viewpager.widget.ViewPager; import androidx.viewpager.widget.ViewPager;
import androidx.drawerlayout.widget.DrawerLayout; import androidx.drawerlayout.widget.DrawerLayout;
import androidx.appcompat.app.ActionBarDrawerToggle; import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.RatingBar; import android.widget.RatingBar;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import org.secuso.privacyfriendlysudoku.controller.GameController;
import org.secuso.privacyfriendlysudoku.controller.GameStateManager; import org.secuso.privacyfriendlysudoku.controller.GameStateManager;
import org.secuso.privacyfriendlysudoku.controller.NewLevelManager; import org.secuso.privacyfriendlysudoku.controller.NewLevelManager;
import org.secuso.privacyfriendlysudoku.controller.helper.GameInfoContainer; import org.secuso.privacyfriendlysudoku.controller.helper.GameInfoContainer;
import org.secuso.privacyfriendlysudoku.game.GameDifficulty; import org.secuso.privacyfriendlysudoku.game.GameDifficulty;
import org.secuso.privacyfriendlysudoku.game.GameType; import org.secuso.privacyfriendlysudoku.game.GameType;
import org.secuso.privacyfriendlysudoku.ui.listener.IImportDialogFragmentListener;
import org.secuso.privacyfriendlysudoku.ui.view.R; import org.secuso.privacyfriendlysudoku.ui.view.R;
import org.secuso.privacyfriendlysudoku.ui.view.databinding.DialogFragmentImportBoardBinding;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import static org.secuso.privacyfriendlysudoku.ui.TutorialActivity.ACTION_SHOW_ANYWAYS; import static org.secuso.privacyfriendlysudoku.ui.TutorialActivity.ACTION_SHOW_ANYWAYS;
public class MainActivity extends BaseActivity implements NavigationView.OnNavigationItemSelectedListener{ public class MainActivity extends BaseActivity implements NavigationView.OnNavigationItemSelectedListener, IImportDialogFragmentListener{
RatingBar difficultyBar; RatingBar difficultyBar;
TextView difficultyText; TextView difficultyText;
@ -53,9 +81,20 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
settings = PreferenceManager.getDefaultSharedPreferences(this); settings = PreferenceManager.getDefaultSharedPreferences(this);
if (settings.getBoolean("pref_dark_mode_setting", false )) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
} else if (settings.getBoolean("pref_dark_mode_automatically_by_system", false)) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
} else if(settings.getBoolean("pref_dark_mode_automatically_by_battery", false)){
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY);
} else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
}
super.onCreate(savedInstanceState);
NewLevelManager newLevelManager = NewLevelManager.getInstance(getApplicationContext(), settings); NewLevelManager newLevelManager = NewLevelManager.getInstance(getApplicationContext(), settings);
@ -121,17 +160,44 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig
final LinkedList<GameDifficulty> difficultyList = GameDifficulty.getValidDifficultyList(); final LinkedList<GameDifficulty> difficultyList = GameDifficulty.getValidDifficultyList();
difficultyBar.setNumStars(difficultyList.size()); difficultyBar.setNumStars(difficultyList.size());
difficultyBar.setMax(difficultyList.size()); difficultyBar.setMax(difficultyList.size());
CheckBox createGameBar = (CheckBox) findViewById(R.id.circleButton);
difficultyBar.setOnRatingBarChangeListener(new RatingBar.OnRatingBarChangeListener() { difficultyBar.setOnRatingBarChangeListener(new RatingBar.OnRatingBarChangeListener() {
@Override @Override
public void onRatingChanged(RatingBar ratingBar, float rating, boolean fromUser) { public void onRatingChanged(RatingBar ratingBar, float rating, boolean fromUser) {
if (rating < 1) { createGameBar.setChecked(false);
ratingBar.setRating(1); ((Button) findViewById(R.id.playButton)).setText(R.string.new_game);
if (rating >= 1) {
difficultyText.setText(getString(difficultyList.get((int) ratingBar.getRating() - 1).getStringResID()));
} else {
difficultyText.setText(R.string.difficulty_custom);
createGameBar.setChecked(true);
((Button)findViewById(R.id.playButton)).setText(R.string.create_game);
} }
difficultyText.setText(getString(difficultyList.get((int) ratingBar.getRating() - 1).getStringResID()));
} }
}); });
GameDifficulty lastChosenDifficulty = GameDifficulty.valueOf(settings.getString("lastChosenDifficulty", "Moderate"));
difficultyBar.setRating(GameDifficulty.getValidDifficultyList().indexOf(lastChosenDifficulty) + 1); createGameBar.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
difficultyBar.setRating(0);
((Button)findViewById(R.id.playButton)).setText(R.string.create_game);
}
createGameBar.setChecked(isChecked);
}});
String retrievedDifficulty = settings.getString("lastChosenDifficulty", "Moderate");
GameDifficulty lastChosenDifficulty = GameDifficulty.valueOf(
retrievedDifficulty.equals("Custom")? GameDifficulty.Unspecified.toString() : retrievedDifficulty);
if (lastChosenDifficulty == GameDifficulty.Unspecified) {
difficultyBar.setRating(0);
createGameBar.setChecked(true);
} else {
difficultyBar.setRating(GameDifficulty.getValidDifficultyList().indexOf(lastChosenDifficulty) + 1);
}
/*LayerDrawable stars = (LayerDrawable)difficultyBar.getProgressDrawable(); /*LayerDrawable stars = (LayerDrawable)difficultyBar.getProgressDrawable();
stars.getDrawable(2).setColorFilter(getResources().getColor(R.color.colorPrimary), PorterDuff.Mode.SRC_ATOP);//Color for Stars fully selected stars.getDrawable(2).setColorFilter(getResources().getColor(R.color.colorPrimary), PorterDuff.Mode.SRC_ATOP);//Color for Stars fully selected
stars.getDrawable(1).setColorFilter(getResources().getColor(R.color.middleblue), PorterDuff.Mode.SRC_ATOP);//Color for Stars partially selected stars.getDrawable(1).setColorFilter(getResources().getColor(R.color.middleblue), PorterDuff.Mode.SRC_ATOP);//Color for Stars partially selected
@ -166,8 +232,6 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig
winScreen.show(fm,"win_screen_layout");*/ winScreen.show(fm,"win_screen_layout");*/
} }
public void onClick(View view) { public void onClick(View view) {
Intent i = null; Intent i = null;
@ -184,6 +248,18 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig
break; break;
case R.id.playButton: case R.id.playButton:
GameType gameType = GameType.getValidGameTypes().get(mViewPager.getCurrentItem()); GameType gameType = GameType.getValidGameTypes().get(mViewPager.getCurrentItem());
if (((CheckBox)findViewById(R.id.circleButton)).isChecked()) {
// start CreateSudokuActivity
i = new Intent(this, CreateSudokuActivity.class);
i.putExtra("gameType", gameType.name());
SharedPreferences.Editor editor = settings.edit();
editor.putString("lastChosenGameType", gameType.name());
editor.putString("lastChosenDifficulty", "Custom");
editor.apply();
//i.putExtra("gameDifficulty", GameDifficulty.Easy);
break;
}
int index = difficultyBar.getProgress()-1; int index = difficultyBar.getProgress()-1;
GameDifficulty gameDifficulty = GameDifficulty.getValidDifficultyList().get(index < 0 ? 0 : index); GameDifficulty gameDifficulty = GameDifficulty.getValidDifficultyList().get(index < 0 ? 0 : index);
@ -242,7 +318,7 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig
Button continueButton = (Button)findViewById(R.id.continueButton); Button continueButton = (Button)findViewById(R.id.continueButton);
GameStateManager fm = new GameStateManager(getBaseContext(), settings); GameStateManager fm = new GameStateManager(getBaseContext(), settings);
List<GameInfoContainer> gic = fm.loadGameStateInfo(); List<GameInfoContainer> gic = fm.loadGameStateInfo();
if(gic.size() > 0) { if(gic.size() > 0 && !(gic.size() == 1 && gic.get(0).getID() == GameController.DAILY_SUDOKU_ID)) {
continueButton.setEnabled(true); continueButton.setEnabled(true);
continueButton.setBackgroundResource(R.drawable.standalone_button); continueButton.setBackgroundResource(R.drawable.standalone_button);
} else { } else {
@ -254,7 +330,7 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig
@Override @Override
public boolean onNavigationItemSelected(MenuItem item) { public boolean onNavigationItemSelected(MenuItem item) {
// Handle navigation view item clicks here. // Handle navigation view item clicks here.
final int id = item.getItemId(); int id = item.getItemId();
drawer.closeDrawer(GravityCompat.START); drawer.closeDrawer(GravityCompat.START);
@ -271,9 +347,9 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig
} }
}, NAVDRAWER_LAUNCH_DELAY); }, NAVDRAWER_LAUNCH_DELAY);
// fade out the active activity // fade out the active activity (but not if the user chose to open the ImportBoardDialog)
View mainContent = findViewById(R.id.main_content); View mainContent = findViewById(R.id.main_content);
if (mainContent != null) { if (mainContent != null && id != R.id.nav_import_sudoku) {
mainContent.animate().alpha(0).setDuration(MAIN_CONTENT_FADEOUT_DURATION); mainContent.animate().alpha(0).setDuration(MAIN_CONTENT_FADEOUT_DURATION);
} }
@ -292,11 +368,13 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig
Intent intent; Intent intent;
switch(id) { switch(id) {
case R.id.nav_import_sudoku:
ImportBoardDialog dialog = new ImportBoardDialog();
dialog.show(getFragmentManager(), "ImportDialogFragment");
break;
case R.id.menu_settings_main: case R.id.menu_settings_main:
//open settings //open settings
intent = new Intent(this,SettingsActivity.class); intent = new Intent(this, SettingsActivity.class);
intent.putExtra( PreferenceActivity.EXTRA_SHOW_FRAGMENT, SettingsActivity.GamePreferenceFragment.class.getName() );
intent.putExtra( PreferenceActivity.EXTRA_NO_HEADERS, true );
startActivity(intent); startActivity(intent);
overridePendingTransition(0, 0); overridePendingTransition(0, 0);
break; break;
@ -330,6 +408,12 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig
overridePendingTransition(0, 0); overridePendingTransition(0, 0);
break; break;
case R.id.nav_dailySudoku_main:
intent = new Intent(this, DailySudokuActivity.class);
startActivity(intent);
overridePendingTransition(0, 0);
break;
default: default:
} }
return true; return true;
@ -345,6 +429,106 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
}*/ }*/
public void onImportDialogPositiveClick(String input) {
String inputSudoku = null;
String prefix = "";
StringBuilder errorMessage = new StringBuilder();
// a valid input needs to contain exactly one of the valid prefixes
for (int i = 0; i < GameActivity.validUris.size(); i++) {
prefix = GameActivity.validUris.get(i).getHost().equals("") ?
GameActivity.validUris.get(i).getScheme() + "://" :
GameActivity.validUris.get(i).getScheme() + "://" + GameActivity.validUris.get(i).getHost() + "/";
if (input.startsWith(prefix)) {
inputSudoku = input.replace(prefix, "");
break;
}
String endOfRecord = i == GameActivity.validUris.size() - 1 ? "" : ", ";
errorMessage.append(prefix);
errorMessage.append(endOfRecord);
}
if (inputSudoku == null) {
Toast.makeText(MainActivity.this,
this.getString(R.string.menu_import_wrong_format_custom_sudoku) + " " + errorMessage.toString(), Toast.LENGTH_LONG).show();
return;
}
double size = Math.sqrt(inputSudoku.length());
boolean validSize = false;
// check whether or not the size of the encoded sudoku is valid; if not, notify the user
for (GameType type : GameType.getValidGameTypes()) {
if (type.getSize() == size) {
validSize = true;
break;
}
}
if (!validSize) {
Toast.makeText(MainActivity.this, R.string.failed_to_verify_custom_sudoku_toast, Toast.LENGTH_LONG).show();
return;
}
GameType gameType = Enum.valueOf(GameType.class, "Default_" + (int)size + "x" + (int)size);
//check whether or not the sudoku is valid and has a unique solution
boolean solvable = CreateSudokuActivity.verify(gameType, inputSudoku);
// if the encoded sudoku is solvable, sent the code directly to the GameActivity; if not, notify the user
if (solvable) {
Toast.makeText(MainActivity.this, R.string.finished_verifying_custom_sudoku_toast, Toast.LENGTH_LONG).show();
final Intent intent = new Intent(this, GameActivity.class);
intent.setData(Uri.parse(prefix + inputSudoku));
startActivity(intent);
finish();
} else {
Toast.makeText(MainActivity.this, R.string.failed_to_verify_custom_sudoku_toast, Toast.LENGTH_LONG).show();
}
}
public void onDialogNegativeClick() {
mNavigationView.setCheckedItem(R.id.nav_newgame_main);
}
public static class ImportBoardDialog extends DialogFragment {
private LinkedList<IImportDialogFragmentListener> listeners = new LinkedList<>();
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// Verify that the host activity implements the callback interface
if(activity instanceof IImportDialogFragmentListener) {
listeners.add((IImportDialogFragmentListener) activity);
}
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style.AppTheme_Dialog);
LayoutInflater inflater = getActivity().getLayoutInflater();
DialogFragmentImportBoardBinding binding = DialogFragmentImportBoardBinding.inflate(inflater);
builder.setView(binding.getRoot());
builder.setMessage(R.string.dialog_import_custom_sudoku);
builder.setPositiveButton(R.string.dialog_import_custom_sudoku_positive_button, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
for(IImportDialogFragmentListener l : listeners) {
l.onImportDialogPositiveClick(binding.ver3ImportSudokuEditText.getText().toString());
}
}
})
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
for(IImportDialogFragmentListener l : listeners) {
l.onDialogNegativeClick();
}
}
});
return builder.create();
}
}
/** /**
* A {@link FragmentPagerAdapter} that returns a fragment corresponding to * A {@link FragmentPagerAdapter} that returns a fragment corresponding to

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.ui; package org.secuso.privacyfriendlysudoku.ui;
import android.content.Context; import android.content.Context;

View file

@ -1,192 +1,120 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.ui; package org.secuso.privacyfriendlysudoku.ui;
import android.content.SharedPreferences;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import androidx.appcompat.app.ActionBar;
import android.preference.PreferenceFragment; import android.preference.PreferenceFragment;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;
import org.secuso.privacyfriendlysudoku.ui.SettingsActivity;
import org.secuso.privacyfriendlysudoku.ui.view.R; import org.secuso.privacyfriendlysudoku.ui.view.R;
import java.util.List; public class SettingsActivity extends AppCompatActivity {
/**
* A {@link PreferenceActivity} that presents a set of application settings. On
* handset devices, settings are presented as a single list. On tablets,
* settings are split by category, with category headers shown to the left of
* the list of settings.
* <p>
* See <a href="http://developer.android.com/design/patterns/settings.html">
* Android Design: Settings</a> for design guidelines and the <a
* href="http://developer.android.com/guide/topics/ui/settings.html">Settings
* API Guide</a> for more information on developing a Settings UI.
*/
public class SettingsActivity extends AppCompatPreferenceActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setupActionBar();
overridePendingTransition(0, 0);
View mainContent = findViewById(R.id.main_content);
if (mainContent != null) {
mainContent.setAlpha(0);
mainContent.animate().alpha(1).setDuration(BaseActivity.MAIN_CONTENT_FADEIN_DURATION);
}
}
/**
* Set up the {@link android.app.ActionBar}, if the API is available.
*/
private void setupActionBar() {
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
// Show the Up button in the action bar.
actionBar.setDisplayHomeAsUpEnabled(true);
}
}
@Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
if (!super.onMenuItemSelected(featureId, item)) {
finish();
//NavUtils.navigateUpFromSameTask(this);
}
return true;
}
return super.onMenuItemSelected(featureId, item);
}
/** private static SettingsFragment settingsFragment;
* {@inheritDoc}
*/
@Override
public boolean onIsMultiPane() {
return isXLargeTablet(this);
}
/** private static void recheckNightModeProperties(SharedPreferences sharedPreferences) {
* Helper method to determine if the device has an extra-large screen. For
* example, 10" tablets are extra-large.
*/
private static boolean isXLargeTablet(Context context) {
return (context.getResources().getConfiguration().screenLayout
& Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE;
}
/** if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
* {@inheritDoc}
*/
@Override
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void onBuildHeaders(List<Header> target) {
loadHeadersFromResource(R.xml.pref_settings_headers, target);
}
/** if (sharedPreferences.getBoolean("pref_dark_mode_setting", false)) {
* A preference value change listener that updates the preference's summary
* to reflect its new value.
*/
private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object value) {
String stringValue = value.toString();
if (preference instanceof ListPreference) {
// For list preferences, look up the correct display value in
// the preference's 'entries' list.
ListPreference listPreference = (ListPreference) preference;
int index = listPreference.findIndexOfValue(stringValue);
// Set the summary to reflect the new value.
preference.setSummary(
index >= 0
? listPreference.getEntries()[index]
: null);
settingsFragment.findPreference("pref_dark_mode_automatically_by_system").setEnabled(false);
settingsFragment.findPreference("pref_dark_mode_automatically_by_battery").setEnabled(false);
} else { } else {
// For all other preferences, set the summary to the value's if (sharedPreferences.getBoolean("pref_dark_mode_automatically_by_battery", false) && sharedPreferences.getBoolean("pref_dark_mode_automatically_by_system", false) ) {
// simple string representation. sharedPreferences.edit().putBoolean("pref_dark_mode_automatically_by_battery", false).commit();
preference.setSummary(stringValue); }
settingsFragment.findPreference("pref_dark_mode_automatically_by_system").setEnabled(!sharedPreferences.getBoolean("pref_dark_mode_automatically_by_battery", false));
settingsFragment.findPreference("pref_dark_mode_automatically_by_battery").setEnabled(!sharedPreferences.getBoolean("pref_dark_mode_automatically_by_system", false));
}}
if (sharedPreferences.getBoolean("pref_dark_mode_setting", false )) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
} else if (sharedPreferences.getBoolean("pref_dark_mode_automatically_by_system", false)) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
} else if(sharedPreferences.getBoolean("pref_dark_mode_automatically_by_battery", false)){
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY);
} else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
}
}
static SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (key.equals("pref_dark_mode_setting")|| key.equals("pref_dark_mode_automatically_by_system")||key.equals("pref_dark_mode_automatically_by_battery")) {
recheckNightModeProperties(sharedPreferences);
} }
return true;
} }
}; };
/** @Override
* Binds a preference's summary to its value. More specifically, when the protected void onCreate(Bundle savedInstanceState) {
* preference's value is changed, its summary (line of text below the super.onCreate(savedInstanceState);
* preference title) is updated to reflect the value. The summary is also setContentView(R.layout.settings_activity);
* immediately updated upon calling this method. The exact display format is settingsFragment = new SettingsFragment();
* dependent on the type of preference. getSupportFragmentManager()
* .beginTransaction()
* @see #sBindPreferenceSummaryToValueListener .replace(R.id.settings, settingsFragment)
*/ .commit();
private static void bindPreferenceSummaryToValue(Preference preference) { ActionBar actionBar = getSupportActionBar();
// Set the listener to watch for value changes. if (actionBar != null) {
preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener); actionBar.setDisplayHomeAsUpEnabled(true);
// Trigger the listener immediately with the preference's
// current value.
sBindPreferenceSummaryToValueListener.onPreferenceChange(preference,
PreferenceManager
.getDefaultSharedPreferences(preference.getContext())
.getString(preference.getKey(), ""));
}
/**
* This method stops fragment injection in malicious applications.
* Make sure to deny any unknown fragments here.
*/
protected boolean isValidFragment(String fragmentName) {
return PreferenceFragment.class.getName().equals(fragmentName)
|| GamePreferenceFragment.class.getName().equals(fragmentName);
}
/**
* This fragment shows general preferences only. It is used when the
* activity is showing a two-pane settings UI.
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class GamePreferenceFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_settings_general);
setHasOptionsMenu(true);
// Bind the summaries of EditText/List/Dialog/Ringtone preferences
// to their values. When their values change, their summaries are
// updated to reflect the new value, per the Android Design
// guidelines.
//bindPreferenceSummaryToValue(findPreference("example_text"));
//bindPreferenceSummaryToValue(findPreference("example_list"));
} }
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
public static class SettingsFragment extends PreferenceFragmentCompat {
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
int id = item.getItemId(); setPreferencesFromResource(R.xml.pref_settings_general, rootKey);
if (id == android.R.id.home) { SharedPreferences preferenceManager = PreferenceManager
getActivity().finish(); .getDefaultSharedPreferences(getActivity());
//startActivity(new Intent(getActivity(), SettingsActivity.class)); preferenceManager.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
return true; recheckNightModeProperties(preferenceManager);
}
return super.onOptionsItemSelected(item);
} }
} }
} }

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.ui; package org.secuso.privacyfriendlysudoku.ui;
import android.content.Intent; import android.content.Intent;

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.ui; package org.secuso.privacyfriendlysudoku.ui;
import android.content.Context; import android.content.Context;

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.ui; package org.secuso.privacyfriendlysudoku.ui;
import android.content.Context; import android.content.Context;
@ -20,7 +36,6 @@ import android.widget.TextView;
import org.secuso.privacyfriendlysudoku.ui.view.R; import org.secuso.privacyfriendlysudoku.ui.view.R;
/** /**
* Class structure taken from tutorial at http://www.androidhive.info/2016/05/android-build-intro-slider-app/ * Class structure taken from tutorial at http://www.androidhive.info/2016/05/android-build-intro-slider-app/
* @author Karola Marky * @author Karola Marky

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.ui.listener; package org.secuso.privacyfriendlysudoku.ui.listener;
/** /**

View file

@ -0,0 +1,22 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.ui.listener;
public interface IFinalizeDialogFragmentListener {
public void onFinalizeDialogPositiveClick();
public void onDialogNegativeClick();
}

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.ui.listener; package org.secuso.privacyfriendlysudoku.ui.listener;
/** /**

View file

@ -0,0 +1,22 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.ui.listener;
public interface IImportDialogFragmentListener {
public void onImportDialogPositiveClick(String input);
public void onDialogNegativeClick();
}

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.ui.listener; package org.secuso.privacyfriendlysudoku.ui.listener;
/** /**

View file

@ -0,0 +1,22 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.ui.listener;
public interface IShareDialogFragmentListener {
public void onShareDialogPositiveClick(String input);
public void onDialogNegativeClick();
}

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.ui.view; package org.secuso.privacyfriendlysudoku.ui.view;
/** /**

View file

@ -0,0 +1,69 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.ui.view;
import androidx.annotation.DrawableRes;
import java.util.ArrayList;
import java.util.List;
public enum CreateSudokuButtonType {
Unspecified(R.drawable.ic_accessibility_black_48dp),// placeholder
Value(R.drawable.ic_accessibility_black_48dp), // should be non picture
Do(R.drawable.ic_redo_black_48dp),
Undo(R.drawable.ic_undo_black_48dp),
Import(R.drawable.ic_import),
Spacer(R.drawable.ic_accessibility_black_48dp),//placeholder
Delete(R.drawable.ic_delete_black_48dp),
Finalize(R.drawable.ic_finalize),
Reset(R.drawable.ic_settings_backup_restore_black_48dp);
private int resID;
CreateSudokuButtonType(@DrawableRes int res){
this.resID = res;
}
public int getResID() {
return resID;
}
public static List<CreateSudokuButtonType> getSpecialButtons() {
ArrayList<CreateSudokuButtonType> result = new ArrayList<CreateSudokuButtonType>();
result.add(Undo);
result.add(Do);
result.add(Finalize);
//result.add(Spacer);
result.add(Delete);
result.add(Import);
return result;
}
public static String getName(CreateSudokuButtonType type) {
switch (type) {
case Do: return "Do";
case Undo: return "Un";
case Finalize: return "Fi";
case Import: return "Im";
case Spacer: return "";
case Delete: return "Del";
default:return "NotSet";
}
}
}

View file

@ -0,0 +1,36 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.ui.view;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ImageButton;
public class CreateSudokuSpecialButton extends androidx.appcompat.widget.AppCompatImageButton {
private int value = -1;
private CreateSudokuButtonType type = CreateSudokuButtonType.Unspecified;
public CreateSudokuSpecialButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setValue(int value) { this.value = value; }
public void setType(CreateSudokuButtonType type) { this.type = type; }
public int getValue () { return value; }
public CreateSudokuButtonType getType() { return type; }
}

View file

@ -0,0 +1,233 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.ui.view;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.FragmentManager;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Toast;
import androidx.core.content.ContextCompat;
import org.secuso.privacyfriendlysudoku.controller.GameController;
import org.secuso.privacyfriendlysudoku.game.listener.IHighlightChangedListener;
import org.secuso.privacyfriendlysudoku.ui.GameActivity;
import org.secuso.privacyfriendlysudoku.ui.MainActivity;
import org.secuso.privacyfriendlysudoku.ui.listener.IFinalizeDialogFragmentListener;
import org.secuso.privacyfriendlysudoku.ui.listener.IHintDialogFragmentListener;
import org.secuso.privacyfriendlysudoku.ui.listener.IImportDialogFragmentListener;
import java.util.LinkedList;
import static org.secuso.privacyfriendlysudoku.ui.view.CreateSudokuButtonType.Spacer;
import static org.secuso.privacyfriendlysudoku.ui.view.CreateSudokuButtonType.getSpecialButtons;
public class CreateSudokuSpecialButtonLayout extends LinearLayout implements IHighlightChangedListener {
IFinalizeDialogFragmentListener finalizeDialogFragmentListener;
IImportDialogFragmentListener importDialogFragmentListener;
CreateSudokuSpecialButton[] fixedButtons;
public int fixedButtonsCount = getSpecialButtons().size();
GameController gameController;
SudokuKeyboardLayout keyboard;
Bitmap bitMap,bitResult;
Canvas canvas;
FragmentManager fragmentManager;
Context context;
OnClickListener listener = new OnClickListener() {
@Override
public void onClick(View v) {
if(v instanceof CreateSudokuSpecialButton) {
CreateSudokuSpecialButton btn = (CreateSudokuSpecialButton)v;
//int row = gameController.getSelectedRow();
//int col = gameController.getSelectedCol();
switch(btn.getType()) {
case Delete:
gameController.deleteSelectedCellsValue();
break;
case Import:
MainActivity.ImportBoardDialog impDialog = new MainActivity.ImportBoardDialog();
impDialog.show(fragmentManager, "ImportDialogFragment");
break;
case Do:
gameController.ReDo();
break;
case Undo:
gameController.UnDo();
break;
case Finalize:
FinalizeConfirmationDialog dialog = new FinalizeConfirmationDialog();
dialog.show(fragmentManager, "FinalizeDialogFragment");
default:
break;
}
}
}
};
public CreateSudokuSpecialButtonLayout(Context context, AttributeSet attrs) {
super(context, attrs);
setWeightSum(fixedButtonsCount);
this.context = context;
}
public void setButtonsEnabled(boolean enabled) {
for(CreateSudokuSpecialButton b : fixedButtons) {
b.setEnabled(enabled);
}
}
public void setButtons(int width, GameController gc, SudokuKeyboardLayout key, FragmentManager fm,
int orientation, Context cxt, IFinalizeDialogFragmentListener finalizeListener,
IImportDialogFragmentListener importListener) {
fragmentManager = fm;
keyboard=key;
gameController = gc;
context = cxt;
finalizeDialogFragmentListener = finalizeListener;
importDialogFragmentListener = importListener;
if(gameController != null) {
gameController.registerHighlightChangedListener(this);
}
fixedButtons = new CreateSudokuSpecialButton[fixedButtonsCount];
LayoutParams p;
int i = 0;
//ArrayList<SudokuButtonType> type = (ArrayList<SudokuButtonType>) SudokuButtonType.getSpecialButtons();
for (CreateSudokuButtonType t : getSpecialButtons()){
fixedButtons[i] = new CreateSudokuSpecialButton(getContext(),null);
if(orientation == LinearLayout.HORIZONTAL) {
p = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, 1);
} else {
p = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, 1);
fixedButtons[i].setPadding(25, 0, 25, 0);
}
p.setMargins(5, 5, 5, 5);
//int width2 =width/(fixedButtonsCount);
//p.width= width2-15;
if(t == Spacer) {
fixedButtons[i].setVisibility(View.INVISIBLE);
}
/*if(t == SudokuButtonType.Do && !gameController.isRedoAvailable()) {
fixedButtons[i].setEnabled(false);
}
if(t == SudokuButtonType.Undo && !gameController.isUndoAvailable()) {
fixedButtons[i].setEnabled(false);
}*/
fixedButtons[i].setLayoutParams(p);
fixedButtons[i].setType(t);
fixedButtons[i].setImageDrawable(getResources().getDrawable(t.getResID()));
// fixedButtons[i].setText(SudokuButtonType.getName(t));
fixedButtons[i].setScaleType(ImageView.ScaleType.CENTER);
fixedButtons[i].setAdjustViewBounds(true);
fixedButtons[i].setOnClickListener(listener);
fixedButtons[i].setBackgroundResource(R.drawable.numpad_highlighted_four);
addView(fixedButtons[i]);
if(fixedButtons[i].getVisibility() == View.VISIBLE) {
Drawable drawable = ContextCompat.getDrawable(context, fixedButtons[i].getType().getResID());
setUpVectorDrawable(drawable);
drawable.draw(canvas);
fixedButtons[i].setImageBitmap(bitResult);
}
i++;
}
}
@Override
public void onHighlightChanged() {
for(int i = 0; i < fixedButtons.length; i++) {
switch(fixedButtons[i].getType()) {
case Undo:
fixedButtons[i].setBackgroundResource(gameController.isUndoAvailable() ?
R.drawable.numpad_highlighted_four : R.drawable.inactive_button);
break;
case Do:
fixedButtons[i].setBackgroundResource(gameController.isRedoAvailable() ?
R.drawable.numpad_highlighted_four : R.drawable.inactive_button);
break;
default:
break;
}
}
}
/*
Set up the vector drawables so that they can be properly displayed despite using theme attributes for their fill color
*/
private void setUpVectorDrawable(Drawable drawable) {
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
bitMap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
bitResult = Bitmap.createBitmap(bitMap.getWidth(), bitMap.getHeight(), Bitmap.Config.ARGB_8888);
canvas = new Canvas(bitResult);
}
public static class FinalizeConfirmationDialog extends DialogFragment {
LinkedList<IFinalizeDialogFragmentListener> listeners = new LinkedList<>();
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// Verify that the host activity implements the callback interface
if(activity instanceof IFinalizeDialogFragmentListener) {
listeners.add((IFinalizeDialogFragmentListener) activity);
}
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Use the Builder class for convenient dialog construction
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style.AppTheme_Dialog);
builder.setMessage(R.string.finalize_custom_sudoku_dialog)
.setPositiveButton(R.string.okay, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
for(IFinalizeDialogFragmentListener l : listeners) {
l.onFinalizeDialogPositiveClick();
}
}
})
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// User cancelled the dialog
}
});
return builder.create();
}
}
}

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.ui.view; package org.secuso.privacyfriendlysudoku.ui.view;
import android.content.Context; import android.content.Context;
@ -12,7 +28,7 @@ import android.widget.Button;
public class SudokuButton extends Button { public class SudokuButton extends androidx.appcompat.widget.AppCompatButton {
private int value = -1; private int value = -1;
private SudokuButtonType type = SudokuButtonType.Unspecified; private SudokuButtonType type = SudokuButtonType.Unspecified;

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.ui.view; package org.secuso.privacyfriendlysudoku.ui.view;
import androidx.annotation.DrawableRes; import androidx.annotation.DrawableRes;

View file

@ -1,6 +1,24 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.ui.view; package org.secuso.privacyfriendlysudoku.ui.view;
import android.content.Context; import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Paint; import android.graphics.Paint;
@ -31,15 +49,32 @@ public class SudokuCellView extends View {
Symbol symbolsToUse = Symbol.Default; Symbol symbolsToUse = Symbol.Default;
RelativeLayout.LayoutParams params; RelativeLayout.LayoutParams params;
int backgroundColor;
public SudokuCellView(Context context) { int backgroundErrorColor;
super(context); int backgroundSelectedColor;
} int backgroundConnectedOuterColor;
int backgroundConnectedInnerColor;
int backgroundValueHighlightedColor;
int backgroundValueHighlightedSelectedColor;
int textColor;
public SudokuCellView(Context context, AttributeSet attrs){ public SudokuCellView(Context context, AttributeSet attrs){
super(context, attrs); super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SudokuCellView);
backgroundColor = a.getColor(R.styleable.SudokuCellView_sudokuCellBackgroundColor, Color.argb(255, 200, 200, 200));
backgroundErrorColor = a.getColor(R.styleable.SudokuCellView_sudokuCellBackgroundErrorColor, Color.argb(255, 200, 200, 200));
backgroundSelectedColor = a.getColor(R.styleable.SudokuCellView_sudokuCellBackgroundSelectedColor, Color.argb(255, 200, 200, 200));
backgroundConnectedOuterColor = a.getColor(R.styleable.SudokuCellView_sudokuCellBackgroundConnectedOuterColor, Color.argb(255, 200, 200, 200));
backgroundConnectedInnerColor = a.getColor(R.styleable.SudokuCellView_sudokuCellBackgroundConnectedInnerColor, Color.argb(255, 200, 200, 200));
backgroundValueHighlightedColor = a.getColor(R.styleable.SudokuCellView_sudokuCellBackgroundValueHighlightedColor, Color.argb(255, 200, 200, 200));
backgroundValueHighlightedSelectedColor = a.getColor(R.styleable.SudokuCellView_sudokuCellBackgroundValueHighlightedSelectedColor, Color.argb(255, 200, 200, 200));
textColor = a.getColor(R.styleable.SudokuCellView_sudokuCellTextColor, Color.argb(255, 200, 200, 200));
a.recycle();
} }
public void setSelected(boolean b) { public void setSelected(boolean b) {
this.selected = b; this.selected = b;
} }
@ -89,13 +124,6 @@ public class SudokuCellView extends View {
return true; return true;
}*/ }*/
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
}
@Override @Override
public void onDraw(Canvas canvas) { public void onDraw(Canvas canvas) {
super.onDraw(canvas); super.onDraw(canvas);
@ -108,27 +136,28 @@ public class SudokuCellView extends View {
Paint p = new Paint(); Paint p = new Paint();
switch(highlightType) { switch(highlightType) {
case Default: case Default:
p.setColor(Color.WHITE); p.setColor(backgroundColor);
break; break;
case Error: case Error:
p.setColor(Color.LTGRAY); p.setColor(backgroundErrorColor);
break; break;
case Selected: case Selected:
p.setColor(Color.GREEN); p.setColor(backgroundSelectedColor);
break; break;
case Connected: case Connected:
p.setColor(Color.WHITE); p.setColor(backgroundConnectedOuterColor);
drawBackground(canvas, 3, 3, mWidth - 3, mHeight - 3, p); drawBackground(canvas, 3, 3, mWidth - 3, mHeight - 3, p);
p.setColor(Color.YELLOW); p.setColor(backgroundConnectedInnerColor);
p.setAlpha(100); p.setAlpha(100);
break; break;
case Value_Highlighted: case Value_Highlighted:
p.setColor(Color.YELLOW); p.setColor(backgroundValueHighlightedColor);
break; break;
case Value_Highlighted_Selected: case Value_Highlighted_Selected:
p.setColor(Color.CYAN); p.setColor(backgroundValueHighlightedSelectedColor);
break;
default: default:
p.setColor(Color.WHITE); p.setColor(backgroundColor);
} }
@ -147,6 +176,7 @@ public class SudokuCellView extends View {
public void drawValue(Canvas canvas) { public void drawValue(Canvas canvas) {
Paint p = new Paint(); Paint p = new Paint();
p.setColor(textColor);
int root = (int) Math.sqrt(size); int root = (int) Math.sqrt(size);
int j= root+1; int j= root+1;
int k = root; int k = root;

View file

@ -1,7 +1,25 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.ui.view; package org.secuso.privacyfriendlysudoku.ui.view;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Paint; import android.graphics.Paint;
@ -31,6 +49,9 @@ public class SudokuFieldLayout extends RelativeLayout implements IHighlightChang
private int gameCellHeight; private int gameCellHeight;
private SharedPreferences settings; private SharedPreferences settings;
private Paint p = new Paint(); private Paint p = new Paint();
int backgroundColor;
int errorColor;
int sectionLineColor;
private OnTouchListener listener = new OnTouchListener() { private OnTouchListener listener = new OnTouchListener() {
@Override @Override
@ -55,8 +76,15 @@ public class SudokuFieldLayout extends RelativeLayout implements IHighlightChang
public SudokuFieldLayout(Context context, AttributeSet attrs) { public SudokuFieldLayout(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
this.attrs=attrs; this.attrs=attrs;
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SudokuFieldLayout);
backgroundColor = a.getColor(R.styleable.SudokuFieldLayout_sudokuFieldGridColor, Color.argb(255, 200, 200, 200));
errorColor = a.getColor(R.styleable.SudokuFieldLayout_sudokuFieldErrorColor, Color.RED);
sectionLineColor = a.getColor(R.styleable.SudokuFieldLayout_sudokuFieldSectionLineColor, Color.BLACK);
a.recycle();
setWillNotDraw(false); setWillNotDraw(false);
setBackgroundColor(Color.argb(255, 200, 200, 200)); setBackgroundColor(backgroundColor);
} }
public void setSettingsAndGame(SharedPreferences sharedPref, GameController gc) { public void setSettingsAndGame(SharedPreferences sharedPref, GameController gc) {
@ -98,7 +126,7 @@ public class SudokuFieldLayout extends RelativeLayout implements IHighlightChang
if(gameController == null) return; if(gameController == null) return;
p.setColor(Color.BLACK); p.setColor(sectionLineColor);
p.setStrokeWidth(5); p.setStrokeWidth(5);
int horizontalSections = gameController.getSize() / sectionWidth; int horizontalSections = gameController.getSize() / sectionWidth;
@ -222,7 +250,7 @@ public class SudokuFieldLayout extends RelativeLayout implements IHighlightChang
p = new Paint(Paint.ANTI_ALIAS_FLAG); p = new Paint(Paint.ANTI_ALIAS_FLAG);
p.setStyle(Paint.Style.STROKE); p.setStyle(Paint.Style.STROKE);
p.setStrokeWidth(4); p.setStrokeWidth(4);
p.setColor(Color.RED); p.setColor(errorColor);
float offsetX = 0; float offsetX = 0;
float offsetY = 0; float offsetY = 0;

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.ui.view; package org.secuso.privacyfriendlysudoku.ui.view;
import android.content.Context; import android.content.Context;

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.ui.view; package org.secuso.privacyfriendlysudoku.ui.view;
import android.content.Context; import android.content.Context;
@ -7,7 +23,7 @@ import android.widget.ImageButton;
/** /**
* Created by TMZ_LToP on 07.12.2015. * Created by TMZ_LToP on 07.12.2015.
*/ */
public class SudokuSpecialButton extends ImageButton { public class SudokuSpecialButton extends androidx.appcompat.widget.AppCompatImageButton {
private int value = -1; private int value = -1;
private SudokuButtonType type = SudokuButtonType.Unspecified; private SudokuButtonType type = SudokuButtonType.Unspecified;

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.ui.view; package org.secuso.privacyfriendlysudoku.ui.view;
import android.app.Activity; import android.app.Activity;
@ -10,6 +26,7 @@ import android.content.DialogInterface;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.Bundle; import android.os.Bundle;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.View; import android.view.View;
@ -18,8 +35,11 @@ import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.Toast; import android.widget.Toast;
import androidx.core.content.ContextCompat;
import org.secuso.privacyfriendlysudoku.controller.GameController; import org.secuso.privacyfriendlysudoku.controller.GameController;
import org.secuso.privacyfriendlysudoku.game.listener.IHighlightChangedListener; import org.secuso.privacyfriendlysudoku.game.listener.IHighlightChangedListener;
import org.secuso.privacyfriendlysudoku.ui.GameActivity;
import org.secuso.privacyfriendlysudoku.ui.listener.IHintDialogFragmentListener; import org.secuso.privacyfriendlysudoku.ui.listener.IHintDialogFragmentListener;
import java.util.LinkedList; import java.util.LinkedList;
@ -40,6 +60,7 @@ public class SudokuSpecialButtonLayout extends LinearLayout implements IHighligh
Bitmap bitMap,bitResult; Bitmap bitMap,bitResult;
Canvas canvas; Canvas canvas;
FragmentManager fragmentManager; FragmentManager fragmentManager;
Context context;
OnClickListener listener = new OnClickListener() { OnClickListener listener = new OnClickListener() {
@Override @Override
@ -68,7 +89,7 @@ public class SudokuSpecialButtonLayout extends LinearLayout implements IHighligh
break; break;
case Hint: case Hint:
if(gameController.isValidCellSelected()) { if(gameController.isValidCellSelected()) {
if(gameController.getUsedHints() == 0) { if(gameController.getUsedHints() == 0 && !gameController.gameIsCustom()) {
// are you sure you want to use a hint? // are you sure you want to use a hint?
HintConfirmationDialog hintDialog = new HintConfirmationDialog(); HintConfirmationDialog hintDialog = new HintConfirmationDialog();
hintDialog.show(fragmentManager, "HintDialogFragment"); hintDialog.show(fragmentManager, "HintDialogFragment");
@ -93,6 +114,7 @@ public class SudokuSpecialButtonLayout extends LinearLayout implements IHighligh
public SudokuSpecialButtonLayout(Context context, AttributeSet attrs) { public SudokuSpecialButtonLayout(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
setWeightSum(fixedButtonsCount); setWeightSum(fixedButtonsCount);
this.context = context;
} }
public void setButtonsEnabled(boolean enabled) { public void setButtonsEnabled(boolean enabled) {
@ -101,10 +123,11 @@ public class SudokuSpecialButtonLayout extends LinearLayout implements IHighligh
} }
} }
public void setButtons(int width, GameController gc, SudokuKeyboardLayout key, FragmentManager fm, int orientation) { public void setButtons(int width, GameController gc, SudokuKeyboardLayout key, FragmentManager fm, int orientation, Context cxt) {
fragmentManager = fm; fragmentManager = fm;
keyboard=key; keyboard=key;
gameController = gc; gameController = gc;
context = cxt;
if(gameController != null) { if(gameController != null) {
gameController.registerHighlightChangedListener(this); gameController.registerHighlightChangedListener(this);
} }
@ -142,6 +165,13 @@ public class SudokuSpecialButtonLayout extends LinearLayout implements IHighligh
fixedButtons[i].setOnClickListener(listener); fixedButtons[i].setOnClickListener(listener);
fixedButtons[i].setBackgroundResource(R.drawable.numpad_highlighted_four); fixedButtons[i].setBackgroundResource(R.drawable.numpad_highlighted_four);
addView(fixedButtons[i]); addView(fixedButtons[i]);
if(fixedButtons[i].getVisibility() == View.VISIBLE) {
Drawable drawable = ContextCompat.getDrawable(context, fixedButtons[i].getType().getResID());
setUpVectorDrawable(drawable);
drawable.draw(canvas);
fixedButtons[i].setImageBitmap(bitResult);
}
i++; i++;
} }
@ -160,12 +190,13 @@ public class SudokuSpecialButtonLayout extends LinearLayout implements IHighligh
R.drawable.numpad_highlighted_four : R.drawable.inactive_button); R.drawable.numpad_highlighted_four : R.drawable.inactive_button);
break; break;
case NoteToggle: case NoteToggle:
bitMap = BitmapFactory.decodeResource(getResources(), fixedButtons[i].getType().getResID()); Drawable drawable = ContextCompat.getDrawable(context, fixedButtons[i].getType().getResID());
bitResult = Bitmap.createBitmap(bitMap.getWidth(), bitMap.getHeight(), Bitmap.Config.ARGB_8888); // prepare canvas for the rotation of the note drawable
setUpVectorDrawable(drawable);
canvas = new Canvas(bitResult);
canvas.rotate(gameController.getNoteStatus() ? 45.0f : 0.0f, bitMap.getWidth()/2, bitMap.getHeight()/2); canvas.rotate(gameController.getNoteStatus() ? 45.0f : 0.0f, bitMap.getWidth()/2, bitMap.getHeight()/2);
canvas.drawBitmap(bitMap, 0, 0, null); canvas.drawBitmap(bitMap, 0, 0, null);
drawable.draw(canvas);
fixedButtons[i].setImageBitmap(bitResult); fixedButtons[i].setImageBitmap(bitResult);
fixedButtons[i].setBackgroundResource(gameController.getNoteStatus() ? R.drawable.numpad_highlighted_three : R.drawable.numpad_highlighted_four); fixedButtons[i].setBackgroundResource(gameController.getNoteStatus() ? R.drawable.numpad_highlighted_three : R.drawable.numpad_highlighted_four);
@ -179,6 +210,18 @@ public class SudokuSpecialButtonLayout extends LinearLayout implements IHighligh
} }
} }
/*
Set up the vector drawables so that they can be properly displayed despite using theme attributes for their fill color
*/
private void setUpVectorDrawable(Drawable drawable) {
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
bitMap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
bitResult = Bitmap.createBitmap(bitMap.getWidth(), bitMap.getHeight(), Bitmap.Config.ARGB_8888);
canvas = new Canvas(bitResult);
}
public static class HintConfirmationDialog extends DialogFragment { public static class HintConfirmationDialog extends DialogFragment {
LinkedList<IHintDialogFragmentListener> listeners = new LinkedList<>(); LinkedList<IHintDialogFragmentListener> listeners = new LinkedList<>();
@ -195,7 +238,7 @@ public class SudokuSpecialButtonLayout extends LinearLayout implements IHighligh
@Override @Override
public Dialog onCreateDialog(Bundle savedInstanceState) { public Dialog onCreateDialog(Bundle savedInstanceState) {
// Use the Builder class for convenient dialog construction // Use the Builder class for convenient dialog construction
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style.AppTheme_Dialog);
builder.setMessage(R.string.hint_confirmation) builder.setMessage(R.string.hint_confirmation)
.setPositiveButton(R.string.hint_confirmation_confirm, new DialogInterface.OnClickListener() { .setPositiveButton(R.string.hint_confirmation_confirm, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) { public void onClick(DialogInterface dialog, int id) {

View file

@ -1,3 +1,19 @@
/*
This file is part of Privacy Friendly Sudoku.
Privacy Friendly Sudoku is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or any later version.
Privacy Friendly Sudoku is distributed in the hope
that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Privacy Friendly Sudoku. If not, see <http://www.gnu.org/licenses/>.
*/
package org.secuso.privacyfriendlysudoku.ui.view; package org.secuso.privacyfriendlysudoku.ui.view;
import android.app.AlertDialog; import android.app.AlertDialog;

View file

@ -4,6 +4,6 @@
android:viewportWidth="24.0" android:viewportWidth="24.0"
android:viewportHeight="24.0"> android:viewportHeight="24.0">
<path <path
android:fillColor="#FF000000" android:fillColor="?attr/standardVectorGraphic"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm1,15h-2v-6h2v6zm0,-8h-2V7h2v2z"/> android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm1,15h-2v-6h2v6zm0,-8h-2V7h2v2z"/>
</vector> </vector>

View file

@ -18,17 +18,17 @@
<item android:id="@android:id/background"> <item android:id="@android:id/background">
<bitmap <bitmap
android:src="@drawable/ic_star_black_48dp" android:src="@drawable/ic_star_black_48dp"
android:tint="@color/middlegrey" /> android:tint="?attr/rankingStarNotSelected" />
</item> </item>
<item android:id="@android:id/secondaryProgress"> <item android:id="@android:id/secondaryProgress">
<bitmap <bitmap
android:src="@drawable/ic_star_half_black_48dp" android:src="@drawable/ic_star_half_black_48dp"
android:tint="@color/middlegrey" /> android:tint="?attr/rankingStarNotSelected" />
</item> </item>
<item android:id="@android:id/progress"> <item android:id="@android:id/progress">
<bitmap <bitmap
android:src="@drawable/ic_star_black_48dp" android:src="@drawable/ic_star_black_48dp"
android:tint="@color/colorAccent" /> android:tint="?attr/colorAccent" />
</item> </item>
</layer-list> </layer-list>

View file

@ -0,0 +1,4 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true" android:drawable="@drawable/ic_circle_blue_36dp"/>
<item android:state_checked="false" android:drawable="@drawable/ic_circle_grey_36dp"/>
</selector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 572 B

View file

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="192dp"
android:height="192dp"
android:viewportWidth="192"
android:viewportHeight="192">
<path
android:pathData="M89.5,17.4c-10.1,4.4 -12.5,17.8 -4.6,25.7 6.5,6.5 15.7,6.5 22.2,-0 12.4,-12.4 -1.5,-32.7 -17.6,-25.7z"
android:fillColor= "?attr/standardVectorGraphic"
android:strokeColor="?attr/standardVectorGraphic"/>
<path
android:pathData="M24,64l0,8 24,-0 24,-0 0,52 0,52 8,-0 8,-0 0,-24 0,-24 8,-0 8,-0 0,24 0,24 8,-0 8,-0 0,-52 0,-52 24,-0 24,-0 0,-8 0,-8 -72,-0 -72,-0 0,8z"
android:fillColor="?attr/standardVectorGraphic"
android:strokeColor="?attr/standardVectorGraphic"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="37dp"
android:height="37dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="?attr/rankingCircleSelected"
android:pathData="M12,12m-10,0a10,10 0,1 1,20 0a10,10 0,1 1,-20 0"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="37dp"
android:height="37dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="?attr/rankingCircleNotSelected"
android:pathData="M12,12m-10,0a10,10 0,1 1,20 0a10,10 0,1 1,-20 0"/>
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 578 B

View file

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="192dp"
android:height="192dp"
android:viewportWidth="192"
android:viewportHeight="192">
<path
android:pathData="M136.5,25.9c-1.6,1 -5.9,4.9 -9.5,8.5l-6.5,6.6 15,15 15,15 8.8,-8.8c6.8,-7 8.7,-9.5 8.7,-11.8 0,-2.3 -2.3,-5.1 -11.8,-14.6 -12.7,-12.7 -14.1,-13.4 -19.7,-9.9z"
android:fillColor="?attr/standardVectorGraphic"
android:strokeColor="?attr/standardVectorGraphic"/>
<path
android:pathData="M67.7,93.8l-43.7,43.7 0,15.3 0,15.2 14.8,-0 14.7,-0 44.5,-44.5 44.5,-44.5 -14.5,-14.5c-8,-8 -14.9,-14.5 -15.5,-14.5 -0.6,-0 -20.7,19.7 -44.8,43.8z"
android:fillColor="?attr/standardVectorGraphic"
android:strokeColor="?attr/standardVectorGraphic"/>
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 498 B

View file

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="192dp"
android:height="192dp"
android:viewportWidth="192"
android:viewportHeight="192">
<path
android:pathData="M71.5,28l-3.9,4 -13.8,-0 -13.8,-0 0,8 0,8 56,-0 56,-0 0,-8 0,-8 -13.8,-0 -13.8,-0 -3.9,-4 -3.9,-4 -20.6,-0 -20.6,-0 -3.9,4z"
android:fillColor="?attr/standardVectorGraphic"
android:strokeColor="?attr/standardVectorGraphic"/>
<path
android:pathData="M48.2,106.9c0.3,49.7 0.4,51 2.4,53.7 1.1,1.5 3.3,3.7 4.8,4.8 2.7,2 3.9,2.1 40.6,2.1 36.7,-0 37.9,-0.1 40.6,-2.1 1.5,-1.1 3.7,-3.3 4.8,-4.8 2,-2.7 2.1,-4 2.4,-53.7l0.3,-50.9 -48.1,-0 -48.1,-0 0.3,50.9z"
android:fillColor="?attr/standardVectorGraphic"
android:strokeColor="?attr/standardVectorGraphic"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="192dp"
android:height="192dp"
android:viewportWidth="192"
android:viewportHeight="192">
<path
android:pathData="M109,89.5l-30.5,30.5 -12,-12 -12.1,-12 -3.9,4 -3.9,4 16.2,16.3 16.2,16.2 34,-34c18.7,-18.7 34,-34.5 34,-35 0,-0.6 -1.7,-2.8 -3.8,-4.8l-3.7,-3.7 -30.5,30.5z"
android:fillColor="?attr/standardVectorGraphic"
android:strokeColor="?attr/standardVectorGraphic"/>
</vector>

View file

@ -0,0 +1,4 @@
<vector android:height="192dp" android:viewportHeight="24"
android:viewportWidth="24" android:width="192dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="?attr/standardVectorGraphic" android:pathData="m12 2v10l2 -2h1l2 2v-10zm-9 1v19h18v-19h-3v16h-12v-13h5v-3z"/>
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 321 B

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="36dp"
android:height="36dp"
android:viewportWidth="36"
android:viewportHeight="36">
<path
android:pathData="M11.2,4.7c-7.1,3.5 -10.2,13.3 -6.4,20.3 2.3,4.2 8.4,8 13,8 7.9,-0 15.2,-7.3 15.2,-15.2 -0.1,-10.6 -12.1,-17.9 -21.8,-13.1zM19,12c0,0.5 -0.4,1 -1,1 -0.5,-0 -1,-0.5 -1,-1 0,-0.6 0.5,-1 1,-1 0.6,-0 1,0.4 1,1zM19,21c0,2.2 -0.4,4 -1,4 -0.5,-0 -1,-1.8 -1,-4 0,-2.2 0.5,-4 1,-4 0.6,-0 1,1.8 1,4z"
android:fillColor="?attr/standardVectorGraphic"
android:strokeColor="?attr/standardVectorGraphic"/>
</vector>

View file

@ -4,6 +4,6 @@
android:viewportWidth="24.0" android:viewportWidth="24.0"
android:viewportHeight="24.0"> android:viewportHeight="24.0">
<path <path
android:fillColor="#FF000000" android:fillColor="?attr/standardVectorGraphic"
android:pathData="M15.41,16.09l-4.58,-4.59 4.58,-4.59L14,5.5l-6,6 6,6z"/> android:pathData="M15.41,16.09l-4.58,-4.59 4.58,-4.59L14,5.5l-6,6 6,6z"/>
</vector> </vector>

View file

@ -4,6 +4,6 @@
android:viewportWidth="24.0" android:viewportWidth="24.0"
android:viewportHeight="24.0"> android:viewportHeight="24.0">
<path <path
android:fillColor="#FF000000" android:fillColor="?attr/standardVectorGraphic"
android:pathData="M8.59,16.34l4.58,-4.59 -4.58,-4.59L10,5.75l6,6 -6,6z"/> android:pathData="M8.59,16.34l4.58,-4.59 -4.58,-4.59L10,5.75l6,6 -6,6z"/>
</vector> </vector>

View file

@ -5,32 +5,32 @@
android:viewportHeight="512"> android:viewportHeight="512">
<path <path
android:pathData="M174.65,41.63h13.43v429.87h-13.43z" android:pathData="M174.65,41.63h13.43v429.87h-13.43z"
android:fillColor="#fff"/> android:fillColor="?attr/lightestBackGround"/>
<path <path
android:pathData="M322.42,41.63h13.43v429.87h-13.43z" android:pathData="M322.42,41.63h13.43v429.87h-13.43z"
android:fillColor="#fff"/> android:fillColor="?attr/lightestBackGround"/>
<path <path
android:pathData="M40.31,337.17l0,-13.43l429.87,-0l0,13.43z" android:pathData="M40.31,337.17l0,-13.43l429.87,-0l0,13.43z"
android:fillColor="#fff"/> android:fillColor="?attr/lightestBackGround"/>
<path <path
android:pathData="M40.31,189.4l0,-13.43l429.87,-0l0,13.43z" android:pathData="M40.31,189.4l0,-13.43l429.87,-0l0,13.43z"
android:fillColor="#fff"/> android:fillColor="?attr/lightestBackGround"/>
<path <path
android:pathData="M94.05,68.49h40.3V55.06H94.05a26.87,26.87 0,0 0,0 53.73h13.43a13.44,13.44 0,1 1,0 26.87H67.18v13.43h40.3a26.87,26.87 0,0 0,0 -53.73H94.05a13.44,13.44 0,0 1,0 -26.87Z" android:pathData="M94.05,68.49h40.3V55.06H94.05a26.87,26.87 0,0 0,0 53.73h13.43a13.44,13.44 0,1 1,0 26.87H67.18v13.43h40.3a26.87,26.87 0,0 0,0 -53.73H94.05a13.44,13.44 0,0 1,0 -26.87Z"
android:fillColor="#fff"/> android:fillColor="?attr/lightestBackGround"/>
<path <path
android:pathData="M262,206.19h-40.3v94L262,300.19a26.86,26.86 0,0 0,26.86 -26.87v-40.3A26.86,26.86 0,0 0,262 206.19ZM262,286.79L235.1,286.79L235.1,219.62L262,219.62a13.43,13.43 0,0 1,13.43 13.43v40.3A13.43,13.43 0,0 1,262 286.79Z" android:pathData="M262,206.19h-40.3v94L262,300.19a26.86,26.86 0,0 0,26.86 -26.87v-40.3A26.86,26.86 0,0 0,262 206.19ZM262,286.79L235.1,286.79L235.1,219.62L262,219.62a13.43,13.43 0,0 1,13.43 13.43v40.3A13.43,13.43 0,0 1,262 286.79Z"
android:fillColor="#fff"/> android:fillColor="?attr/lightestBackGround"/>
<path <path
android:pathData="M406.38,206.19L392.94,206.19a26.86,26.86 0,0 0,-26.86 26.86v40.3a26.86,26.86 0,0 0,26.86 26.87h13.44a26.87,26.87 0,0 0,26.86 -26.87v-40.3A26.87,26.87 0,0 0,406.38 206.19ZM419.81,273.35a13.48,13.48 0,0 1,-13.43 13.44L392.94,286.79a13.47,13.47 0,0 1,-13.43 -13.44v-40.3a13.47,13.47 0,0 1,13.43 -13.43h13.44a13.48,13.48 0,0 1,13.43 13.43Z" android:pathData="M406.38,206.19L392.94,206.19a26.86,26.86 0,0 0,-26.86 26.86v40.3a26.86,26.86 0,0 0,26.86 26.87h13.44a26.87,26.87 0,0 0,26.86 -26.87v-40.3A26.87,26.87 0,0 0,406.38 206.19ZM419.81,273.35a13.48,13.48 0,0 1,-13.43 13.44L392.94,286.79a13.47,13.47 0,0 1,-13.43 -13.44v-40.3a13.47,13.47 0,0 1,13.43 -13.43h13.44a13.48,13.48 0,0 1,13.43 13.43Z"
android:fillColor="#fff"/> android:fillColor="?attr/lightestBackGround"/>
<path <path
android:pathData="M134.35,374.01l0,0l0,0l0,0l0,-9.5l-9.36,0l-0.04,0.1l-0.1,-0.1l-37.91,37.91l-6.32,6.32l0,-12.56l0,-31.67l-13.44,0l0,19.82l0,17.34l0,20.51l0,17.64l0,18.72l13.44,0l0,-30.8l6.45,-6.45l37.78,37.25l0,0l9.5,0l0,-9.36l-37.85,-37.33l37.84,-37.84l0.01,0z" android:pathData="M134.35,374.01l0,0l0,0l0,0l0,-9.5l-9.36,0l-0.04,0.1l-0.1,-0.1l-37.91,37.91l-6.32,6.32l0,-12.56l0,-31.67l-13.44,0l0,19.82l0,17.34l0,20.51l0,17.64l0,18.72l13.44,0l0,-30.8l6.45,-6.45l37.78,37.25l0,0l9.5,0l0,-9.36l-37.85,-37.33l37.84,-37.84l0.01,0z"
android:fillColor="#fff"/> android:fillColor="?attr/lightestBackGround"/>
<path <path
android:pathData="M275.4,58.42v60.45a20.15,20.15 0,1 1,-40.3 0V58.42H221.67v60.45a33.58,33.58 0,1 0,67.16 0V58.42Z" android:pathData="M275.4,58.42v60.45a20.15,20.15 0,1 1,-40.3 0V58.42H221.67v60.45a33.58,33.58 0,1 0,67.16 0V58.42Z"
android:fillColor="#fff"/> android:fillColor="?attr/lightestBackGround"/>
<path <path
android:pathData="M275.4,364.51V425a20.15,20.15 0,1 1,-40.3 0V364.51H221.67V425a33.58,33.58 0,1 0,67.16 0V364.51Z" android:pathData="M275.4,364.51V425a20.15,20.15 0,1 1,-40.3 0V364.51H221.67V425a33.58,33.58 0,1 0,67.16 0V364.51Z"
android:fillColor="#fff"/> android:fillColor="?attr/lightestBackGround"/>
</vector> </vector>

View file

@ -2,16 +2,16 @@
xmlns:aapt="http://schemas.android.com/aapt" xmlns:aapt="http://schemas.android.com/aapt"
android:width="512dp" android:width="512dp"
android:height="512dp" android:height="512dp"
android:viewportWidth="512" android:viewportWidth="600"
android:viewportHeight="512"> android:viewportHeight="600">
<path <path
android:pathData="M510.91,471.7V216.47L336.27,41.83H323.68V93.05L289.26,58.62H275.82v40.3l-40.3,-40.3H222.09l0,16.77L188.51,41.83H175.07l0.12,54.69L134.35,55.68 88.6,62 71.8,76.25l2.52,17.64 5,6.71 -5.88,-0.84 38.62,38.62 -44.49,-2.52V149.3l27,27 -53.83,-0.1V189.6L174.23,323.09H40.74v14.28l26.87,26.86v94L121.34,512H470.61A40.42,40.42 0,0 0,510.91 471.7Z"> android:pathData="M659,552.9V364.5L336.3,41.8h-12.6V93l-34.4,-34.4h-13.4v40.3l-40.3,-40.3h-13.4v16.8l-33.6,-33.6h-13.4l0.1,54.7l-40.8,-40.8L88.6,62L71.8,76.2l2.5,17.6l5,6.7l-5.9,-0.8l38.6,38.6l-44.5,-2.5v13.4l27,27l-53.8,-0.1v13.4l133.5,133.5H40.7v14.3l26.9,26.9v94l141.5,141.6L633,611.9C655.2,611.8 658.9,575.1 659,552.9z">
<aapt:attr name="android:fillColor"> <aapt:attr name="android:fillColor">
<gradient <gradient
android:startY="43.94" android:startY="30.4022"
android:startX="119.85" android:startX="143.3293"
android:endY="551.52" android:endY="680.6151"
android:endX="412.9" android:endX="518.728"
android:type="linear"> android:type="linear">
<item android:offset="0" android:color="#FF004492"/> <item android:offset="0" android:color="#FF004492"/>
<item android:offset="0.22" android:color="#F9004593"/> <item android:offset="0.22" android:color="#F9004593"/>
@ -25,33 +25,33 @@
</aapt:attr> </aapt:attr>
</path> </path>
<path <path
android:pathData="M174.65,41.63h13.43v429.87h-13.43z" android:pathData="M174.6,41.6h13.4v429.9h-13.4z"
android:fillColor="#fff"/> android:fillColor="#FFFFFF"/>
<path <path
android:pathData="M322.42,41.63h13.43v429.87h-13.43z" android:pathData="M322.4,41.6h13.4v429.9h-13.4z"
android:fillColor="#fff"/> android:fillColor="#FFFFFF"/>
<path <path
android:pathData="M40.31,337.17l0,-13.43l429.87,-0l0,13.43z" android:pathData="M40.3,323.7h429.9v13.4h-429.9z"
android:fillColor="#fff"/> android:fillColor="#FFFFFF"/>
<path <path
android:pathData="M40.31,189.4l0,-13.43l429.87,-0l0,13.43z" android:pathData="M40.3,176h429.9v13.4h-429.9z"
android:fillColor="#fff"/> android:fillColor="#FFFFFF"/>
<path <path
android:pathData="M94.05,68.49h40.3V55.06H94.05a26.87,26.87 0,0 0,0 53.73h13.43a13.44,13.44 0,1 1,0 26.87H67.18v13.43h40.3a26.87,26.87 0,0 0,0 -53.73H94.05a13.44,13.44 0,0 1,0 -26.87Z" android:pathData="M94.1,68.5h40.3V55.1H94.1c-14.8,0.3 -26.6,12.5 -26.3,27.4c0.3,14.4 11.9,26.1 26.3,26.3h13.4c7.4,-0.2 13.6,5.6 13.8,13.1c0.2,7.4 -5.6,13.6 -13.1,13.8c-0.2,0 -0.5,0 -0.7,0H67.2v13.4h40.3c14.8,-0.3 26.6,-12.5 26.3,-27.4c-0.3,-14.4 -11.9,-26.1 -26.3,-26.3H94.1C86.6,95.2 80.8,89 81,81.6C81.2,74.4 86.9,68.7 94.1,68.5z"
android:fillColor="#fff"/> android:fillColor="#FFFFFF"/>
<path <path
android:pathData="M262,206.19h-40.3v94L262,300.19a26.86,26.86 0,0 0,26.86 -26.87v-40.3A26.86,26.86 0,0 0,262 206.19ZM262,286.79L235.1,286.79L235.1,219.62L262,219.62a13.43,13.43 0,0 1,13.43 13.43v40.3A13.43,13.43 0,0 1,262 286.79Z" android:pathData="M262,206.2h-40.3v94H262c14.8,0 26.9,-12 26.9,-26.9c0,0 0,0 0,0V233C288.8,218.2 276.8,206.2 262,206.2zM262,286.8h-26.9v-67.2H262c7.4,0 13.4,6 13.4,13.4v40.3C275.4,280.8 269.4,286.8 262,286.8C262,286.8 262,286.8 262,286.8z"
android:fillColor="#fff"/> android:fillColor="#FFFFFF"/>
<path <path
android:pathData="M406.38,206.19L392.94,206.19a26.86,26.86 0,0 0,-26.86 26.86v40.3a26.86,26.86 0,0 0,26.86 26.87h13.44a26.87,26.87 0,0 0,26.86 -26.87v-40.3A26.87,26.87 0,0 0,406.38 206.19ZM419.81,273.35a13.48,13.48 0,0 1,-13.43 13.44L392.94,286.79a13.47,13.47 0,0 1,-13.43 -13.44v-40.3a13.47,13.47 0,0 1,13.43 -13.43h13.44a13.48,13.48 0,0 1,13.43 13.43Z" android:pathData="M406.4,206.2h-13.4c-14.8,0 -26.9,12 -26.9,26.9v40.3c0,14.8 12,26.9 26.9,26.9c0,0 0,0 0,0h13.4c14.8,0 26.9,-12 26.9,-26.9v-40.3C433.2,218.2 421.2,206.2 406.4,206.2zM419.8,273.4c0,7.4 -6,13.4 -13.4,13.4h-13.4c-7.4,0 -13.4,-6 -13.4,-13.4v-40.3c0,-7.4 6,-13.4 13.4,-13.4h13.4c7.4,0 13.4,6 13.4,13.4V273.4z"
android:fillColor="#fff"/> android:fillColor="#FFFFFF"/>
<path <path
android:pathData="M134.35,374.01l0,0l0,0l0,0l0,-9.5l-9.36,0l-0.04,0.1l-0.1,-0.1l-37.91,37.91l-6.32,6.32l0,-12.56l0,-31.67l-13.44,0l0,19.82l0,17.34l0,20.51l0,17.64l0,18.72l13.44,0l0,-30.8l6.45,-6.45l37.78,37.25l0,0l9.5,0l0,-9.36l-37.85,-37.33l37.84,-37.84l0.01,0z" android:pathData="M134.4,374l0,0l0,0l0,0l0,-9.5l-9.4,0l-0.1,0.1l-0.1,-0.1l-37.9,37.9l-6.3,6.3l0,-12.5l0,-31.7l-13.4,0l0,19.8l0,17.4l0,20.5l0,17.6l0,18.7l13.4,0l0,-30.8l6.5,-6.4l37.7,37.2l0,0l9.6,0l0,-9.3l-37.9,-37.3l37.8,-37.9z"
android:fillColor="#fff"/> android:fillColor="#FFFFFF"/>
<path <path
android:pathData="M275.4,58.42v60.45a20.15,20.15 0,1 1,-40.3 0V58.42H221.67v60.45a33.58,33.58 0,1 0,67.16 0V58.42Z" android:pathData="M275.4,58.4v60.4c0,11.1 -9,20.2 -20.1,20.2s-20.2,-9 -20.2,-20.2l0,0V58.4h-13.4v60.4c0,18.5 15,33.6 33.6,33.6c18.5,0 33.6,-15 33.6,-33.6V58.4H275.4z"
android:fillColor="#fff"/> android:fillColor="#FFFFFF"/>
<path <path
android:pathData="M275.4,364.51V425a20.15,20.15 0,1 1,-40.3 0V364.51H221.67V425a33.58,33.58 0,1 0,67.16 0V364.51Z" android:pathData="M275.4,364.5V425c0,11.1 -9,20.1 -20.1,20.1s-20.2,-9 -20.2,-20.1l0,0v-60.5h-13.4V425c0,18.5 15,33.6 33.6,33.6c18.5,0 33.6,-15 33.6,-33.6v-60.5H275.4z"
android:fillColor="#fff"/> android:fillColor="#FFFFFF"/>
</vector> </vector>

View file

@ -0,0 +1,57 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="512dp"
android:height="512dp"
android:viewportWidth="512"
android:viewportHeight="512">
<path
android:pathData="M510.91,471.7V216.47L336.27,41.83H323.68V93.05L289.26,58.62H275.82v40.3l-40.3,-40.3H222.09l0,16.77L188.51,41.83H175.07l0.12,54.69L134.35,55.68 88.6,62 71.8,76.25l2.52,17.64 5,6.71 -5.88,-0.84 38.62,38.62 -44.49,-2.52V149.3l27,27 -53.83,-0.1V189.6L174.23,323.09H40.74v14.28l26.87,26.86v94L121.34,512H470.61A40.42,40.42 0,0 0,510.91 471.7Z">
<aapt:attr name="android:fillColor">
<gradient
android:startY="43.94"
android:startX="119.85"
android:endY="551.52"
android:endX="412.9"
android:type="linear">
<item android:offset="0" android:color="#FF004492"/>
<item android:offset="0.22" android:color="#F9004593"/>
<item android:offset="0.39" android:color="#E8024894"/>
<item android:offset="0.55" android:color="#C9044E97"/>
<item android:offset="0.7" android:color="#A007569B"/>
<item android:offset="0.84" android:color="#6B0A60A0"/>
<item android:offset="0.97" android:color="#280F6CA6"/>
<item android:offset="1" android:color="#19106FA8"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M174.65,41.63h13.43v429.87h-13.43z"
android:fillColor="#fff"/>
<path
android:pathData="M322.42,41.63h13.43v429.87h-13.43z"
android:fillColor="#fff"/>
<path
android:pathData="M40.31,337.17l0,-13.43l429.87,-0l0,13.43z"
android:fillColor="#fff"/>
<path
android:pathData="M40.31,189.4l0,-13.43l429.87,-0l0,13.43z"
android:fillColor="#fff"/>
<path
android:pathData="M94.05,68.49h40.3V55.06H94.05a26.87,26.87 0,0 0,0 53.73h13.43a13.44,13.44 0,1 1,0 26.87H67.18v13.43h40.3a26.87,26.87 0,0 0,0 -53.73H94.05a13.44,13.44 0,0 1,0 -26.87Z"
android:fillColor="#fff"/>
<path
android:pathData="M262,206.19h-40.3v94L262,300.19a26.86,26.86 0,0 0,26.86 -26.87v-40.3A26.86,26.86 0,0 0,262 206.19ZM262,286.79L235.1,286.79L235.1,219.62L262,219.62a13.43,13.43 0,0 1,13.43 13.43v40.3A13.43,13.43 0,0 1,262 286.79Z"
android:fillColor="#fff"/>
<path
android:pathData="M406.38,206.19L392.94,206.19a26.86,26.86 0,0 0,-26.86 26.86v40.3a26.86,26.86 0,0 0,26.86 26.87h13.44a26.87,26.87 0,0 0,26.86 -26.87v-40.3A26.87,26.87 0,0 0,406.38 206.19ZM419.81,273.35a13.48,13.48 0,0 1,-13.43 13.44L392.94,286.79a13.47,13.47 0,0 1,-13.43 -13.44v-40.3a13.47,13.47 0,0 1,13.43 -13.43h13.44a13.48,13.48 0,0 1,13.43 13.43Z"
android:fillColor="#fff"/>
<path
android:pathData="M134.35,374.01l0,0l0,0l0,0l0,-9.5l-9.36,0l-0.04,0.1l-0.1,-0.1l-37.91,37.91l-6.32,6.32l0,-12.56l0,-31.67l-13.44,0l0,19.82l0,17.34l0,20.51l0,17.64l0,18.72l13.44,0l0,-30.8l6.45,-6.45l37.78,37.25l0,0l9.5,0l0,-9.36l-37.85,-37.33l37.84,-37.84l0.01,0z"
android:fillColor="#fff"/>
<path
android:pathData="M275.4,58.42v60.45a20.15,20.15 0,1 1,-40.3 0V58.42H221.67v60.45a33.58,33.58 0,1 0,67.16 0V58.42Z"
android:fillColor="#fff"/>
<path
android:pathData="M275.4,364.51V425a20.15,20.15 0,1 1,-40.3 0V364.51H221.67V425a33.58,33.58 0,1 0,67.16 0V364.51Z"
android:fillColor="#fff"/>
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="192dp"
android:height="192dp"
android:viewportWidth="192"
android:viewportHeight="192">
<path
android:pathData="M84.5,17.2c-9,1.6 -20.7,8.1 -27.9,15.4 -22.3,22.2 -21.8,57.6 1.1,80l6.3,6.1 0,10.2c0,9.5 0.2,10.4 2.5,12.6l2.4,2.5 27.1,-0 27.1,-0 2.4,-2.5c2.3,-2.2 2.5,-3.1 2.5,-12.6l0,-10.2 6.3,-6.1c23,-22.4 23.4,-57.6 1.1,-80 -13.4,-13.4 -31.9,-19 -50.9,-15.4zM113.6,36.4c8.6,4.3 14.3,10.2 18.8,19.6 2.8,5.9 3.1,7.4 3.1,16 0,8.3 -0.4,10.3 -2.8,15.4 -3.6,7.7 -8.1,13.1 -15.1,18.1l-5.6,4 0,9.2 0,9.3 -16,-0 -16,-0 0,-9.3 0,-9.2 -5.1,-3.5c-7,-4.7 -11.6,-10.1 -15.3,-17.9 -2.8,-6 -3.1,-7.5 -3.1,-16.1 0,-8.6 0.3,-10.1 3.2,-16.2 4,-8.5 9.7,-14.7 17.2,-18.6 7.9,-4.3 12.2,-5.2 21.5,-4.7 6.6,0.3 9.1,0.9 15.2,3.9z"
android:fillColor="?attr/standardVectorGraphic"
android:strokeColor="?attr/standardVectorGraphic"/>
<path
android:pathData="M72,165.5c0,4.6 0.4,6 2.5,8 2.4,2.5 2.6,2.5 21.5,2.5 18.9,-0 19.1,-0 21.5,-2.5 2.1,-2 2.5,-3.4 2.5,-8l0,-5.5 -24,-0 -24,-0 0,5.5z"
android:fillColor="?attr/standardVectorGraphic"
android:strokeColor="?attr/standardVectorGraphic"/>
</vector>

View file

@ -4,6 +4,6 @@
android:viewportWidth="24.0" android:viewportWidth="24.0"
android:viewportHeight="24.0"> android:viewportHeight="24.0">
<path <path
android:fillColor="#FF000000" android:fillColor="?attr/standardVectorGraphic"
android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/> android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/>
</vector> </vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="?attr/standardVectorGraphic"
android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z"/>
</vector>

View file

@ -4,6 +4,6 @@
android:viewportWidth="24.0" android:viewportWidth="24.0"
android:viewportHeight="24.0"> android:viewportHeight="24.0">
<path <path
android:fillColor="#FF000000" android:fillColor="?attr/standardVectorGraphic"
android:pathData="M11,18h2v-2h-2v2zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM12,6c-2.21,0 -4,1.79 -4,4h2c0,-1.1 0.9,-2 2,-2s2,0.9 2,2c0,2 -3,1.75 -3,5h2c0,-2.25 3,-2.5 3,-5 0,-2.21 -1.79,-4 -4,-4z"/> android:pathData="M11,18h2v-2h-2v2zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM12,6c-2.21,0 -4,1.79 -4,4h2c0,-1.1 0.9,-2 2,-2s2,0.9 2,2c0,2 -3,1.75 -3,5h2c0,-2.25 3,-2.5 3,-5 0,-2.21 -1.79,-4 -4,-4z"/>
</vector> </vector>

View file

@ -4,6 +4,6 @@
android:viewportWidth="24.0" android:viewportWidth="24.0"
android:viewportHeight="24.0"> android:viewportHeight="24.0">
<path <path
android:fillColor="#FF000000" android:fillColor="?attr/standardVectorGraphic"
android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z"/> android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z"/>
</vector> </vector>

Some files were not shown because too many files have changed in this diff Show more